-
Notifications
You must be signed in to change notification settings - Fork 7
RE1-T123 Modern notification sounds #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,3 +41,4 @@ expo-env.d.ts | |
| .mcp.json | ||
| opencode.json | ||
| /.dual-graph-pro | ||
| .DS_Store | ||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/components/settings/__tests__/modern-notification-sounds-item.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { describe, expect, it, jest, beforeEach } from '@jest/globals'; | ||
| import { fireEvent, render, screen } from '@testing-library/react-native'; | ||
| import React from 'react'; | ||
|
|
||
| import { ModernNotificationSoundsItem } from '../modern-notification-sounds-item'; | ||
|
|
||
| // Mock the translation hook | ||
| jest.mock('react-i18next', () => ({ | ||
| useTranslation: () => ({ | ||
| t: (key: string) => { | ||
| const translations: Record<string, string> = { | ||
| 'settings.modern_notification_sounds': 'Modern Notification Sounds', | ||
| 'settings.modern_notification_sounds_description': 'Use the new modern sound set for push notifications.', | ||
| }; | ||
| return translations[key] || key; | ||
| }, | ||
| }), | ||
| })); | ||
|
|
||
| // Control the platform per test (component is Android-only). | ||
| let mockIsAndroid = true; | ||
| jest.mock('@/lib/platform', () => ({ | ||
| get isAndroid() { | ||
| return mockIsAndroid; | ||
| }, | ||
| })); | ||
|
|
||
| // Control the preference hook. | ||
| const mockSetModernSoundsEnabled = jest.fn(); | ||
| let mockIsModernSoundsEnabled = true; | ||
| jest.mock('@/lib/hooks/use-modern-notification-sounds', () => ({ | ||
| useModernNotificationSounds: () => ({ | ||
| isModernSoundsEnabled: mockIsModernSoundsEnabled, | ||
| setModernSoundsEnabled: mockSetModernSoundsEnabled, | ||
| }), | ||
| })); | ||
|
|
||
| describe('ModernNotificationSoundsItem', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| mockIsAndroid = true; | ||
| mockIsModernSoundsEnabled = true; | ||
| }); | ||
|
|
||
| it('renders the label and description on Android', () => { | ||
| render(<ModernNotificationSoundsItem />); | ||
|
|
||
| expect(screen.getByText('Modern Notification Sounds')).toBeTruthy(); | ||
| expect(screen.getByText('Use the new modern sound set for push notifications.')).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('renders nothing on non-Android platforms', () => { | ||
| mockIsAndroid = false; | ||
|
|
||
| render(<ModernNotificationSoundsItem />); | ||
|
|
||
| expect(screen.queryByText('Modern Notification Sounds')).toBeNull(); | ||
| }); | ||
|
|
||
| it('reflects the enabled state on the switch', () => { | ||
| mockIsModernSoundsEnabled = true; | ||
|
|
||
| render(<ModernNotificationSoundsItem />); | ||
|
|
||
| expect(screen.getByRole('switch').props.value).toBe(true); | ||
| }); | ||
|
|
||
| it('calls the setter when toggled off', () => { | ||
| mockIsModernSoundsEnabled = true; | ||
|
|
||
| render(<ModernNotificationSoundsItem />); | ||
|
|
||
| fireEvent(screen.getByRole('switch'), 'valueChange', false); | ||
|
|
||
| expect(mockSetModernSoundsEnabled).toHaveBeenCalledWith(false); | ||
| }); | ||
| }); |
45 changes: 45 additions & 0 deletions
45
src/components/settings/modern-notification-sounds-item.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { useColorScheme } from 'nativewind'; | ||
| import React from 'react'; | ||
| import { useTranslation } from 'react-i18next'; | ||
|
|
||
| import { useModernNotificationSounds } from '@/lib/hooks/use-modern-notification-sounds'; | ||
| import { isAndroid } from '@/lib/platform'; | ||
|
|
||
| import { Switch } from '../ui/switch'; | ||
| import { Text } from '../ui/text'; | ||
| import { View } from '../ui/view'; | ||
| import { VStack } from '../ui/vstack'; | ||
|
|
||
| export const ModernNotificationSoundsItem = () => { | ||
| const { isModernSoundsEnabled, setModernSoundsEnabled } = useModernNotificationSounds(); | ||
| const { t } = useTranslation(); | ||
| const { colorScheme } = useColorScheme(); | ||
|
|
||
| const handleToggle = React.useCallback( | ||
| (value: boolean) => { | ||
| setModernSoundsEnabled(value); | ||
| }, | ||
| [setModernSoundsEnabled] | ||
| ); | ||
|
|
||
| // Notification channel sounds are an Android-only concept; hide on other platforms. | ||
| if (!isAndroid) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <VStack space="sm"> | ||
| <View className="flex-1 flex-row items-center justify-between px-4 py-2"> | ||
| <View className="flex-1 flex-row items-center pr-3"> | ||
| <Text>{t('settings.modern_notification_sounds')}</Text> | ||
| </View> | ||
| <View className="flex-row items-center"> | ||
| <Switch size="md" value={isModernSoundsEnabled} onValueChange={handleToggle} /> | ||
| </View> | ||
| </View> | ||
| <View className="px-4"> | ||
| <Text className={`text-xs ${colorScheme === 'dark' ? 'text-neutral-400' : 'text-neutral-500'}`}>{t('settings.modern_notification_sounds_description')}</Text> | ||
| </View> | ||
| </VStack> | ||
| ); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import React from 'react'; | ||
| import { Platform } from 'react-native'; | ||
| import { useMMKVBoolean } from 'react-native-mmkv'; | ||
|
|
||
| import { storage } from '@/lib/storage'; | ||
| import { MODERN_NOTIFICATION_SOUNDS_ENABLED } from '@/lib/storage/notification-prefs'; | ||
| import { pushNotificationService } from '@/services/push-notification'; | ||
|
|
||
| /** | ||
| * Android-only hook for the "use modern notification sounds" preference. | ||
| * | ||
| * Defaults to enabled (modern sounds) and is persisted in MMKV alongside the | ||
| * other app settings. When toggled on Android, the notification channels are | ||
| * recreated so the new sound takes effect — a channel's sound is immutable | ||
| * after it is created, so it must be deleted and recreated to change it. | ||
| */ | ||
| export const useModernNotificationSounds = () => { | ||
| const [enabled, _setEnabled] = useMMKVBoolean(MODERN_NOTIFICATION_SOUNDS_ENABLED, storage); | ||
|
|
||
| const setModernSoundsEnabled = React.useCallback( | ||
| async (value: boolean) => { | ||
| _setEnabled(value); | ||
| if (Platform.OS === 'android') { | ||
| await pushNotificationService.refreshAndroidNotificationChannels(); | ||
| } | ||
| }, | ||
| [_setEnabled] | ||
| ); | ||
|
|
||
| // Default ON when the user has not set a preference. | ||
| const isModernSoundsEnabled = enabled ?? true; | ||
| return { isModernSoundsEnabled, setModernSoundsEnabled } as const; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { storage } from '@/lib/storage'; | ||
|
|
||
| /** | ||
| * MMKV key for the Android-only "use modern notification sounds" preference. | ||
| * Defaults to enabled (modern sounds) when the user has not set a preference. | ||
| */ | ||
| export const MODERN_NOTIFICATION_SOUNDS_ENABLED = 'MODERN_NOTIFICATION_SOUNDS_ENABLED'; | ||
|
|
||
| /** | ||
| * MMKV key tracking which sound set the Android notification channels were last | ||
| * created with. Android notification channel sound is immutable after creation, | ||
| * so this marker lets us detect when channels need to be deleted and recreated. | ||
| */ | ||
| const NOTIFICATION_SOUND_MODE_APPLIED = 'NOTIFICATION_SOUND_MODE_APPLIED'; | ||
|
|
||
| export type NotificationSoundMode = 'modern' | 'classic'; | ||
|
|
||
| /** | ||
| * Whether modern notification sounds are enabled. Defaults to true (modern is | ||
| * the default) when the user has not made a choice. | ||
| */ | ||
| export const getModernNotificationSoundsEnabled = (): boolean => storage.getBoolean(MODERN_NOTIFICATION_SOUNDS_ENABLED) ?? true; | ||
|
|
||
| /** | ||
| * The sound mode the Android channels were last created with, or undefined if | ||
| * they have never been created (fresh install or app upgrade). | ||
| */ | ||
| export const getAppliedNotificationSoundMode = (): NotificationSoundMode | undefined => { | ||
| const mode = storage.getString(NOTIFICATION_SOUND_MODE_APPLIED); | ||
| return mode === 'modern' || mode === 'classic' ? mode : undefined; | ||
| }; | ||
|
|
||
| /** Persist the sound mode the Android channels were created with. */ | ||
| export const setAppliedNotificationSoundMode = (mode: NotificationSoundMode): void => { | ||
| storage.set(NOTIFICATION_SOUND_MODE_APPLIED, mode); | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unhandled promise rejection from
pushNotificationService.refreshAndroidNotificationChannels()at line 24 risks leaving the app in an inconsistent state if_setEnabled(value)at line 22 already updated the MMKV preference. Wrap the awaited operations in a try/catch block with structured error logging to match the patterns used in sibling hooks likeuseKeepAliveanduseBackgroundGeolocation.Kody rule violation: Handle async operations with proper error handling
Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.