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
12 changes: 6 additions & 6 deletions src/app/(app)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -503,11 +503,11 @@ const styles = StyleSheet.create({
markerOuterRingPulseWeb:
Platform.OS === 'web'
? {
// @ts-ignore — web-only CSS animation properties
animationName: 'pulse-ring',
animationDuration: '2s',
animationIterationCount: 'infinite',
animationTimingFunction: 'ease-in-out',
}
// @ts-ignore — web-only CSS animation properties
animationName: 'pulse-ring',
animationDuration: '2s',
animationIterationCount: 'infinite',
animationTimingFunction: 'ease-in-out',
}
: ({} as any),
});
139 changes: 30 additions & 109 deletions src/app/call/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useColorScheme } from 'nativewind';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native';
import WebView from 'react-native-webview';

import { Loading } from '@/components/common/loading';
import ZeroState from '@/components/common/zero-state';
Expand All @@ -16,6 +15,7 @@ import { Box } from '@/components/ui/box';
import { Button, ButtonIcon, ButtonText } from '@/components/ui/button';
import { Heading } from '@/components/ui/heading';
import { HStack } from '@/components/ui/hstack';
import { HtmlRenderer } from '@/components/ui/html-renderer';
import { SharedTabs, type TabItem } from '@/components/ui/shared-tabs';
import { Text } from '@/components/ui/text';
import { VStack } from '@/components/ui/vstack';
Expand Down Expand Up @@ -145,17 +145,26 @@ export default function CallDetail() {

useEffect(() => {
if (call) {
// Try Latitude/Longitude first, but validate they are real coordinates
if (call.Latitude && call.Longitude) {
setCoordinates({
latitude: parseFloat(call.Latitude),
longitude: parseFloat(call.Longitude),
});
} else if (call.Geolocation) {
const [lat, lng] = call.Geolocation.split(',');
setCoordinates({
latitude: parseFloat(lat),
longitude: parseFloat(lng),
});
const lat = parseFloat(call.Latitude);
const lng = parseFloat(call.Longitude);
if (!isNaN(lat) && !isNaN(lng) && (lat !== 0 || lng !== 0)) {
setCoordinates({ latitude: lat, longitude: lng });
return;
}
}

// Fall through to Geolocation if Latitude/Longitude are missing or invalid
if (call.Geolocation) {
const parts = call.Geolocation.split(',');
if (parts.length === 2) {
const lat = parseFloat(parts[0].trim());
const lng = parseFloat(parts[1].trim());
if (!isNaN(lat) && !isNaN(lng)) {
setCoordinates({ latitude: lat, longitude: lng });
}
}
}
}
}, [call]);
Expand Down Expand Up @@ -186,7 +195,7 @@ export default function CallDetail() {
* Opens the device's native maps application with directions to the call location
*/
const handleRoute = async () => {
if (!coordinates.latitude || !coordinates.longitude) {
if (coordinates.latitude === null || coordinates.longitude === null) {
showToast('error', t('call_detail.no_location_for_routing'));
return;
}
Expand Down Expand Up @@ -300,37 +309,7 @@ export default function CallDetail() {
<Box className="border-b border-outline-100 pb-2">
<Text className="text-sm text-gray-500">{t('call_detail.note')}</Text>
<Box>
<WebView
style={[styles.container, { height: 200 }]}
originWhitelist={['*']}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
source={{
html: `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body {
color: ${textColor};
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 0;
font-size: 16px;
line-height: 1.5;
}
* {
max-width: 100%;
}
</style>
</head>
<body>${call.Note}</body>
</html>
`,
}}
androidLayerType="software"
/>
<HtmlRenderer html={call.Note ?? ''} style={StyleSheet.flatten([styles.container, { height: 200 }])} />
</Box>
</Box>
</VStack>
Expand Down Expand Up @@ -377,37 +356,7 @@ export default function CallDetail() {
<Text className="font-semibold">{protocol.Name}</Text>
<Text className="text-sm text-gray-600">{protocol.Description}</Text>
<Box>
<WebView
style={[styles.container, { height: 200 }]}
originWhitelist={['*']}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
source={{
html: `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body {
color: ${textColor};
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 0;
font-size: 16px;
line-height: 1.5;
}
* {
max-width: 100%;
}
</style>
</head>
<body>${protocol.ProtocolText}</body>
</html>
`,
}}
androidLayerType="software"
/>
<HtmlRenderer html={protocol.ProtocolText ?? ''} style={StyleSheet.flatten([styles.container, { height: 200 }])} />
</Box>
</Box>
))}
Expand Down Expand Up @@ -506,45 +455,17 @@ export default function CallDetail() {
</HStack>
<VStack className="space-y-1">
<Box style={{ height: 80 }}>
<WebView
style={[styles.container, { height: 80 }]}
originWhitelist={['*']}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
source={{
html: `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body {
color: ${textColor};
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 0;
font-size: 16px;
line-height: 1.5;
}
* {
max-width: 100%;
}
</style>
</head>
<body>${call.Nature}</body>
</html>
`,
}}
androidLayerType="software"
/>
<HtmlRenderer html={call.Nature ?? ''} style={StyleSheet.flatten([styles.container, { height: 80 }])} />
</Box>
</VStack>
</Box>

{/* Map */}
<Box className="w-full">
{coordinates.latitude && coordinates.longitude ? <StaticMap latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} height={200} showUserLocation={true} /> : null}
</Box>
{/* Map - only show when valid coordinates exist */}
{coordinates.latitude !== null && coordinates.longitude !== null ? (
<Box className="w-full">
<StaticMap latitude={coordinates.latitude} longitude={coordinates.longitude} address={call.Address} zoom={15} height={200} showUserLocation={true} />
</Box>
) : null}

{/* Action Buttons */}
<HStack className={`justify-around p-4 shadow-sm ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-neutral-100'}`}>
Expand Down
26 changes: 16 additions & 10 deletions src/app/call/__tests__/[id].security.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ jest.mock('react-native', () => ({
ScrollView: ({ children, ...props }: any) => <div {...props}>{children}</div>,
StyleSheet: {
create: (styles: any) => styles,
flatten: (styles: any) => {
if (Array.isArray(styles)) {
return Object.assign({}, ...styles.filter(Boolean));
}
return styles || {};
},
},
useWindowDimensions: () => ({ width: 375, height: 812 }),
View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
Expand Down Expand Up @@ -273,10 +279,10 @@ jest.mock('@/lib/navigation', () => ({
openMapsWithDirections: jest.fn().mockResolvedValue(true),
}));

// Mock WebView
jest.mock('react-native-webview', () => ({
// Mock HtmlRenderer
jest.mock('@/components/ui/html-renderer', () => ({
__esModule: true,
default: () => <div data-testid="webview">WebView</div>,
HtmlRenderer: () => <div data-testid="html-renderer">HtmlRenderer</div>,
}));

// Mock date-fns
Expand Down Expand Up @@ -317,45 +323,45 @@ describe('CallDetail', () => {

beforeEach(() => {
jest.clearAllMocks();

// Setup stores as selector-based stores
useCallDetailStore.mockImplementation((selector: any) => {
if (selector) {
return selector(mockCallDetailStore);
}
return mockCallDetailStore;
});

useSecurityStore.mockImplementation((selector: any) => typeof selector === 'function' ? selector(mockSecurityStore) : mockSecurityStore);

useCoreStore.mockImplementation((selector: any) => {
if (selector) {
return selector(mockCoreStore);
}
return mockCoreStore;
});

useLocationStore.mockImplementation((selector: any) => {
if (selector) {
return selector(mockLocationStore);
}
return mockLocationStore;
});

useStatusBottomSheetStore.mockImplementation((selector: any) => {
if (selector) {
return selector(mockStatusBottomSheetStore);
}
return mockStatusBottomSheetStore;
});

useToastStore.mockImplementation((selector: any) => {
if (selector) {
return selector(mockToastStore);
}
return mockToastStore;
});

// Setup securityStore as a selector-based store
securityStore.mockImplementation((selector: any) => {
const state = {
Expand Down
6 changes: 3 additions & 3 deletions src/app/call/__tests__/[id].test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ jest.mock('date-fns', () => ({
format: jest.fn(() => '2024-01-01 12:00'),
}));

// Mock react-native-webview
jest.mock('react-native-webview', () => ({
// Mock HtmlRenderer
jest.mock('@/components/ui/html-renderer', () => ({
__esModule: true,
default: 'WebView',
HtmlRenderer: 'HtmlRenderer',
}));

jest.mock('@/hooks/use-analytics', () => ({
Expand Down
34 changes: 2 additions & 32 deletions src/components/calls/call-card.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { AlertTriangle, MapPin, Phone } from 'lucide-react-native';
import React from 'react';
import { StyleSheet } from 'react-native';
import WebView from 'react-native-webview';

import { Box } from '@/components/ui/box';
import { HStack } from '@/components/ui/hstack';
import { HtmlRenderer } from '@/components/ui/html-renderer';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { VStack } from '@/components/ui/vstack';
Expand Down Expand Up @@ -101,37 +101,7 @@ export const CallCard: React.FC<CallCardProps> = ({ call, priority }) => {
{/* Nature of Call */}
{call.Nature && (
<Box className="mt-4 rounded-lg bg-white/50 p-3">
<WebView
style={[styles.container, { height: 80 }]}
originWhitelist={['*']}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
source={{
html: `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<style>
body {
color: ${textColor};
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 0;
font-size: 16px;
line-height: 1.5;
}
* {
max-width: 100%;
}
</style>
</head>
<body>${call.Nature}</body>
</html>
`,
}}
androidLayerType="software"
/>
<HtmlRenderer html={call.Nature} style={StyleSheet.flatten([styles.container, { height: 80 }])} textColor={textColor} />
</Box>
)}
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import React from 'react';
import { ContactDetailsSheet } from '../contact-details-sheet';
import { ContactType, type ContactResultData } from '@/models/v4/contacts/contactResultData';

// Mock react-native-webview
jest.mock('react-native-webview', () => {
// Mock HtmlRenderer
jest.mock('@/components/ui/html-renderer', () => {
const { View } = require('react-native');
return {
__esModule: true,
default: (props: any) => <View testID="mock-webview" {...props} />,
HtmlRenderer: (props: any) => <View testID="mock-webview" {...props} />,
};
});

Expand Down
Loading