From 890febfff4d88766779474964fa582d33aadf5a0 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sat, 16 Aug 2025 13:53:38 -0700 Subject: [PATCH 1/6] CU-868f7hkrj Fixing permissions issue for ios --- src/services/push-notification.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index 3eed9595..49f6d823 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -155,7 +155,18 @@ class PushNotificationService { let finalStatus = existingStatus; if (existingStatus !== 'granted') { - const { status } = await Notifications.requestPermissionsAsync(); + const { status } = await Notifications.requestPermissionsAsync({ + ios: { + allowAlert: true, + allowBadge: true, + allowSound: true, + allowCriticalAlerts: true, + }, + android: { + priority: 'high', + vibrate: true, + }, + }); finalStatus = status; } From f87af2c542505af20bee48002c29c46f60052038 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sat, 16 Aug 2025 15:52:06 -0700 Subject: [PATCH 2/6] CU-868f7hkrj Trying to fix critical notifications and background ptt ios. --- __mocks__/react-native-callkeep.ts | 17 +- src/app/_layout.tsx | 19 +- src/app/login/login-form.tsx | 2 +- .../__tests__/focus-aware-status-bar.test.tsx | 413 ++++++++++++++++++ src/components/ui/focus-aware-status-bar.tsx | 55 ++- .../__tests__/useLiveKitCallStore.test.ts | 114 ++--- .../livekit-call/store/useLiveKitCallStore.ts | 86 +--- .../app-initialization.service.test.ts | 224 ++++++++++ .../__tests__/callkeep.service.ios.test.ts | 134 +++++- src/services/app-initialization.service.ts | 156 +++++++ src/services/callkeep.service.ios.ts | 26 +- .../app/__tests__/livekit-store.test.ts | 21 +- 12 files changed, 1085 insertions(+), 182 deletions(-) create mode 100644 src/components/ui/__tests__/focus-aware-status-bar.test.tsx create mode 100644 src/services/__tests__/app-initialization.service.test.ts create mode 100644 src/services/app-initialization.service.ts diff --git a/__mocks__/react-native-callkeep.ts b/__mocks__/react-native-callkeep.ts index 2103ecc5..e7c6e1f6 100644 --- a/__mocks__/react-native-callkeep.ts +++ b/__mocks__/react-native-callkeep.ts @@ -1,4 +1,4 @@ -export default { +const mockMethods = { setup: jest.fn().mockResolvedValue(undefined), startCall: jest.fn().mockResolvedValue(undefined), reportConnectingOutgoingCallWithUUID: jest.fn().mockResolvedValue(undefined), @@ -12,6 +12,17 @@ export default { backToForeground: jest.fn(), }; -export const AudioSessionCategoryOption = {}; -export const AudioSessionMode = {}; +export default mockMethods; + +export const AudioSessionCategoryOption = { + allowAirPlay: 1, + allowBluetooth: 2, + allowBluetoothA2DP: 4, + defaultToSpeaker: 8, +}; + +export const AudioSessionMode = { + voiceChat: 1, +}; + export const CONSTANTS = {}; diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 90c23e0a..945618d9 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -26,10 +26,10 @@ import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'; import { loadKeepAliveState } from '@/lib/hooks/use-keep-alive'; import { loadSelectedTheme } from '@/lib/hooks/use-selected-theme'; import { logger } from '@/lib/logging'; -import { getDeviceUuid } from '@/lib/storage/app'; -import { setDeviceUuid } from '@/lib/storage/app'; +import { getDeviceUuid, setDeviceUuid } from '@/lib/storage/app'; import { loadBackgroundGeolocationState } from '@/lib/storage/background-geolocation'; import { uuidv4 } from '@/lib/utils'; +import { appInitializationService } from '@/services/app-initialization.service'; export { ErrorBoundary } from 'expo-router'; export const navigationRef = createNavigationContainerRef(); @@ -141,6 +141,21 @@ function RootLayout() { context: { error }, }); }); + + // Initialize global app services (including CallKeep for iOS) + appInitializationService + .initialize() + .then(() => { + logger.info({ + message: 'Global app services initialized successfully', + }); + }) + .catch((error) => { + logger.error({ + message: 'Failed to initialize global app services', + context: { error }, + }); + }); }, [ref]); return ( diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx index 3e2102e5..edb87d60 100644 --- a/src/app/login/login-form.tsx +++ b/src/app/login/login-form.tsx @@ -42,7 +42,7 @@ export type LoginFormProps = { onServerUrlPress?: () => void; }; -export const LoginForm = ({ onSubmit = () => { }, isLoading = false, error = undefined, onServerUrlPress }: LoginFormProps) => { +export const LoginForm = ({ onSubmit = () => {}, isLoading = false, error = undefined, onServerUrlPress }: LoginFormProps) => { const { colorScheme } = useColorScheme(); const { t } = useTranslation(); const { diff --git a/src/components/ui/__tests__/focus-aware-status-bar.test.tsx b/src/components/ui/__tests__/focus-aware-status-bar.test.tsx new file mode 100644 index 00000000..bef04b5b --- /dev/null +++ b/src/components/ui/__tests__/focus-aware-status-bar.test.tsx @@ -0,0 +1,413 @@ +import { useIsFocused } from '@react-navigation/native'; +import * as NavigationBar from 'expo-navigation-bar'; +import { useColorScheme } from 'nativewind'; +import React from 'react'; +import { Platform, StatusBar } from 'react-native'; +import { render } from '@testing-library/react-native'; +import { SystemBars } from 'react-native-edge-to-edge'; + +import { FocusAwareStatusBar } from '../focus-aware-status-bar'; + +// Mock dependencies +jest.mock('@react-navigation/native'); +jest.mock('expo-navigation-bar'); +jest.mock('nativewind'); +jest.mock('react-native-edge-to-edge'); + +const mockUseIsFocused = useIsFocused as jest.MockedFunction; +const mockUseColorScheme = useColorScheme as jest.MockedFunction; +const mockSystemBars = SystemBars as jest.MockedFunction; +const mockNavigationBar = NavigationBar as jest.Mocked; + +// Mock StatusBar methods +const mockStatusBar = { + setBackgroundColor: jest.fn(), + setTranslucent: jest.fn(), + setHidden: jest.fn(), + setBarStyle: jest.fn(), +}; + +// Replace StatusBar with our mock +Object.defineProperty(StatusBar, 'setBackgroundColor', { + value: mockStatusBar.setBackgroundColor, + writable: true, +}); +Object.defineProperty(StatusBar, 'setTranslucent', { + value: mockStatusBar.setTranslucent, + writable: true, +}); +Object.defineProperty(StatusBar, 'setHidden', { + value: mockStatusBar.setHidden, + writable: true, +}); +Object.defineProperty(StatusBar, 'setBarStyle', { + value: mockStatusBar.setBarStyle, + writable: true, +}); + +describe('FocusAwareStatusBar', () => { + const originalPlatform = Platform.OS; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseIsFocused.mockReturnValue(true); + mockUseColorScheme.mockReturnValue({ colorScheme: 'light' } as any); + mockNavigationBar.setVisibilityAsync.mockResolvedValue(); + mockSystemBars.mockReturnValue(null); + }); + + afterEach(() => { + // Reset Platform.OS to original value + Object.defineProperty(Platform, 'OS', { + value: originalPlatform, + writable: true, + }); + }); + + describe('Platform: Android', () => { + beforeEach(() => { + Object.defineProperty(Platform, 'OS', { + value: 'android', + writable: true, + }); + }); + + it('should configure status bar and navigation bar on Android when not hidden', () => { + render(