Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,14 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
bluetoothAlwaysPermission: 'Allow Resgrid Unit to connect to bluetooth devices',
},
],
[
'expo-navigation-bar',
{
position: 'relative',
visibility: 'hidden',
behavior: 'inset-touch',
},
],
'expo-audio',
'@livekit/react-native-expo-plugin',
'@config-plugins/react-native-webrtc',
Expand Down
148 changes: 148 additions & 0 deletions docs/audio-stream-refactoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Audio Stream Store Refactoring

## Overview

The audio stream store has been refactored to use `expo-av` instead of `expo-audio` to resolve issues with playing remote MP3 streams over the internet in the new Expo architecture.

## Key Changes

### 1. Replaced expo-audio with expo-av

**Before:**
```typescript
import { type AudioPlayer, createAudioPlayer } from 'expo-audio';
```

**After:**
```typescript
import { Audio, type AVPlaybackSource, type AVPlaybackStatus } from 'expo-av';
```

### 2. Updated Audio Player Management

**Before:**
- Used `createAudioPlayer()` function
- Audio player instance stored as `AudioPlayer`

**After:**
- Uses `Audio.Sound.createAsync()` method
- Audio player instance stored as `Audio.Sound`

### 3. Enhanced Audio Configuration

Added proper audio mode configuration for streaming:

```typescript
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
staysActiveInBackground: true,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
playThroughEarpieceAndroid: false,
});
```

### 4. Improved State Management

Added new state properties for better stream status tracking:

```typescript
interface AudioStreamState {
// ... existing properties
isLoading: boolean; // Track loading state
isBuffering: boolean; // Track buffering state
soundObject: Audio.Sound | null; // Sound instance
}
```

### 5. Better Error Handling

Enhanced error handling with proper cleanup and status updates.

## Installation

Make sure you have `expo-av` installed:

```bash
yarn add expo-av
```

## Usage Example

```typescript
import { useAudioStreamStore } from '@/stores/app/audio-stream-store';

const MyComponent = () => {
const {
availableStreams,
isLoadingStreams,
currentStream,
isPlaying,
isLoading,
isBuffering,
fetchAvailableStreams,
playStream,
stopStream,
} = useAudioStreamStore();

useEffect(() => {
fetchAvailableStreams();
}, []);

const handlePlay = async (stream) => {
try {
await playStream(stream);
} catch (error) {
console.error('Failed to play stream:', error);
}
};

// ... render logic
};
```

## Benefits

1. **Better Remote Streaming Support**: `expo-av` provides more robust support for remote MP3 streams
2. **Improved Audio Configuration**: Proper audio mode settings for background playback and silent mode
3. **Enhanced Error Handling**: Better error recovery and cleanup
4. **Loading States**: More granular loading and buffering states for better UX
5. **Memory Management**: Proper cleanup of audio resources

## Migration Notes

If you were using the previous audio stream store:

1. Replace any direct `audioPlayer` references with `soundObject`
2. Update any custom audio handling code to use `expo-av` APIs
3. The store API remains largely the same, so most usage code should work without changes

## Troubleshooting

### Common Issues

1. **Audio not playing on iOS in silent mode**: Make sure `playsInSilentModeIOS: true` is set
2. **Buffering issues**: The store now properly tracks buffering state - use `isBuffering` to show loading indicators
3. **Background playback**: Ensure your app has proper background audio permissions configured

### Audio Permissions

Make sure your app's configuration includes proper audio permissions:

**app.json/app.config.js:**
```json
{
"expo": {
"ios": {
"infoPlist": {
"UIBackgroundModes": ["audio"]
}
},
"android": {
"permissions": [
"android.permission.RECORD_AUDIO"
]
}
}
}
```
155 changes: 155 additions & 0 deletions docs/notification-inbox-multiselect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# NotificationInbox Multi-Select Refactor

## Overview

The NotificationInbox component has been refactored to support multi-select functionality with bulk delete operations. Users can now select multiple notifications and delete them all at once with a confirmation modal.

## Features Added

### 1. Multi-Select Mode
- **Long Press to Enter**: Long press any notification to enter selection mode
- **Visual Indicators**: Selected notifications are highlighted with a blue background and checkmark icon
- **Selection Counter**: Shows the count of selected notifications in the header

### 2. Selection Controls
- **Select All/Deselect All**: Toggle button to select or deselect all notifications at once
- **Individual Toggle**: Tap notifications to toggle their selection state in selection mode
- **Cancel**: Exit selection mode without performing any actions

### 3. Bulk Delete
- **Delete Button**: Trash icon button that's only enabled when notifications are selected
- **Confirmation Modal**: Shows a confirmation dialog before deleting multiple notifications
- **Batch Processing**: Iterates through selected notifications and calls the `deleteMessage` API for each
- **Loading State**: Shows loading indicator while deletion is in progress
- **Error Handling**: Displays appropriate toast messages for success/failure

### 4. Enhanced UI/UX
- **Responsive Header**: Changes layout based on normal vs selection mode
- **Loading States**: Visual feedback during delete operations
- **Toast Notifications**: Success/error messages for user feedback
- **Clean State Management**: Resets selection state when component closes

## Technical Implementation

### New State Variables
```typescript
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [selectedNotificationIds, setSelectedNotificationIds] = useState<Set<string>>(new Set());
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const [isDeletingSelected, setIsDeletingSelected] = useState(false);
```

### Key Functions
- `enterSelectionMode()`: Enters multi-select mode
- `exitSelectionMode()`: Exits multi-select mode and clears selections
- `toggleNotificationSelection(id)`: Toggles selection state of a notification
- `selectAllNotifications()`: Selects all visible notifications
- `deselectAllNotifications()`: Clears all selections
- `handleBulkDelete()`: Shows confirmation modal for bulk delete
- `confirmBulkDelete()`: Executes the bulk delete operation

### Component Structure

#### Header Modes
1. **Normal Mode**: Shows "Notifications" title with close and menu buttons
2. **Selection Mode**: Shows selection count with Select All, Delete, and Cancel buttons

#### Notification Items
- **Normal Mode**: Tap to view details, long press to enter selection
- **Selection Mode**: Tap to toggle selection, shows checkmark/circle icons

#### Confirmation Modal
- Uses the existing `Modal` component from `@/components/ui/modal`
- Displays count of notifications to be deleted
- Provides Cancel and Delete buttons

## Usage

### Entering Selection Mode
1. Long press any notification item
2. Or tap the menu (three dots) button in the header (future enhancement)

### Selecting Notifications
1. In selection mode, tap individual notifications to toggle selection
2. Use "Select All" to select all visible notifications
3. Use "Deselect All" to clear all selections

### Bulk Delete
1. Select one or more notifications
2. Tap the trash icon button
3. Confirm deletion in the modal dialog
4. Wait for completion and see success/error toast

### Exiting Selection Mode
1. Tap "Cancel" button
2. Complete a bulk delete operation
3. Close the notification inbox

## API Integration

The component uses the existing `deleteMessage` API function:
```typescript
import { deleteMessage } from '@/api/novu/inbox';
```

Each selected notification is deleted individually using Promise.all:
```typescript
const deletePromises = Array.from(selectedNotificationIds).map((id) => deleteMessage(id));
await Promise.all(deletePromises);
```

## Error Handling

- Network errors during deletion show error toast
- Individual notification delete failures are handled gracefully
- Loading states prevent multiple simultaneous operations
- State is properly reset on errors

## Accessibility

- Visual indicators for selection state
- Clear labeling of actions
- Confirmation dialogs for destructive actions
- Loading states for better user feedback

## Testing

Comprehensive test suite includes:
- Component rendering in different states
- Selection mode entry/exit
- Multi-select functionality
- Bulk delete operations
- Error handling
- State management
- API integration

Run tests with:
```bash
npm test -- --testPathPattern="NotificationInbox.test.tsx"
```

## Styling

New styles added:
- `selectedNotificationItem`: Blue background for selected items
- `selectionIndicator`: Spacing for checkmark/circle icons
- `selectionHeader`: Layout for selection mode header
- `selectionCount`: Styling for selection counter
- `selectionActions`: Layout for selection mode buttons
- `headerActions`: Layout for normal mode header buttons
- `actionButton`: Styling for menu button

## Dependencies

- `lucide-react-native`: Added CheckCircle, Circle, MoreVertical, Trash2 icons
- `@/components/ui/modal`: Used for confirmation dialog
- Existing dependencies: Button, Text, animations, etc.

## Future Enhancements

1. **Keyboard Shortcuts**: Add keyboard support for desktop usage
2. **Swipe Actions**: Swipe to delete individual notifications
3. **Filtering**: Select notifications by type or status
4. **Archive Functionality**: Bulk archive instead of delete
5. **Undo Feature**: Allow undoing bulk deletions
6. **Performance**: Virtual scrolling for large notification lists
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"expo-application": "~6.0.2",
"expo-asset": "~11.0.5",
"expo-audio": "~0.3.5",
"expo-av": "~15.0.2",
"expo-build-properties": "~0.13.3",
"expo-constants": "~17.0.8",
"expo-dev-client": "~5.0.20",
Expand All @@ -110,6 +111,7 @@
"expo-localization": "~16.0.0",
"expo-location": "~18.0.10",
"expo-modules-core": "~2.2.3",
"expo-navigation-bar": "~4.0.9",
"expo-notifications": "~0.29.14",
"expo-router": "~4.0.21",
"expo-screen-orientation": "~8.0.4",
Expand Down
2 changes: 1 addition & 1 deletion src/api/novu/inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const getGroupsApi = createApiEndpoint('/Inbox/DeleteMessage');

export const deleteMessage = async (messageId: string) => {
const response = await getGroupsApi.delete({
groupId: encodeURIComponent(messageId),
messageId: encodeURIComponent(messageId),
Copy link

Copilot AI Jul 20, 2025

Choose a reason for hiding this comment

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

The parameter name was changed from 'groupId' to 'messageId', but the endpoint path still references '/Inbox/DeleteMessage'. Verify that this parameter name matches the expected API contract for the delete message endpoint.

Copilot uses AI. Check for mistakes.
});
return response.data;
};
2 changes: 1 addition & 1 deletion src/api/voice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const getDepartmentVoiceSettingsApi = createApiEndpoint('/Voice/GetDepartmentVoi

const getDepartmentAudioStreamsApi = createCachedApiEndpoint('/Voice/GetDepartmentAudioStreams', {
ttl: 60 * 1000 * 2880, // Cache for 2 days
enabled: true,
enabled: false,
});

export const getDepartmentVoiceSettings = async () => {
Expand Down
Loading
Loading