Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion src/components/audio-stream/audio-stream-bottom-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export const AudioStreamBottomSheet = () => {
) : (
<Select selectedValue={getCurrentStreamValue()} onValueChange={handleStreamSelection} isDisabled={isLoading || isBuffering}>
<SelectTrigger>
<SelectInput placeholder={t('audio_streams.select_placeholder')} />
<SelectInput placeholder={t('audio_streams.select_placeholder')} value={currentStream ? currentStream.Name : t('audio_streams.none')} />
<SelectIcon />
</SelectTrigger>
<SelectPortal>
Expand Down
4 changes: 2 additions & 2 deletions src/components/calendar/calendar-item-details-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ export const CalendarItemDetailsSheet: React.FC<CalendarItemDetailsSheetProps> =

return (
<CustomBottomSheet isOpen={isOpen} onClose={onClose}>
<VStack style={{ height: '80%' }}>
<ScrollView showsVerticalScrollIndicator={true} contentContainerStyle={{ padding: 24 }}>
<VStack style={{ flex: 1 }}>
<ScrollView showsVerticalScrollIndicator={true} contentContainerStyle={{ padding: 24, flexGrow: 1 }}>
{/* Header */}
<VStack className="mb-6">
<HStack className="mb-2 items-start justify-between">
Expand Down
218 changes: 179 additions & 39 deletions src/components/calls/call-images-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,59 +137,199 @@
}, [validImages.length, activeIndex]);

const handleImageSelect = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
showToast('error', t('common.permission_denied'));
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled) {
const asset = result.assets[0];
const filename = asset.fileName || `image_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
try {
// On Web, permissions are handled by the browser, so we skip the permission check
// On iOS/Android, we need to request permissions first
if (Platform.OS !== 'web') {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
// Check if user can be asked again or needs to go to settings
if (!permissionResult.canAskAgain) {
showToast('error', t('callImages.permission_denied_settings'));
} else {
showToast('error', t('common.permission_denied'));
}
return;
}
}

const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
// allowsEditing is only supported on iOS and Android, not on Web
allowsEditing: Platform.OS !== 'web',
quality: 0.8,
});

// Handle Android MainActivity destruction - check for pending results
if (Platform.OS === 'android' && result.canceled) {
const pendingResult = await ImagePicker.getPendingResultAsync();
if (pendingResult && 'assets' in pendingResult && pendingResult.assets && pendingResult.assets.length > 0) {
const asset = pendingResult.assets[0];
if (asset?.uri) {
const filename = asset.fileName || `image_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
return;
}
}
}

if (!result.canceled && result.assets && result.assets.length > 0) {
const asset = result.assets[0];
if (asset?.uri) {
const filename = asset.fileName || `image_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
} else {
console.error('Image picker returned asset without URI');
showToast('error', t('callImages.select_error'));
}
}
} catch (error) {
console.error('Error selecting image from library:', error);
showToast('error', t('callImages.select_error'));
}
};

const handleCameraCapture = async () => {
const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
if (permissionResult.granted === false) {
showToast('error', t('common.permission_denied'));
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 0.8,
});
if (!result.canceled) {
const asset = result.assets[0];
const filename = `camera_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
try {
// On Web, camera permissions are handled by the browser when launching the camera
// On iOS/Android, we need to request camera permissions first
if (Platform.OS !== 'web') {
const permissionResult = await ImagePicker.requestCameraPermissionsAsync();
if (permissionResult.granted === false) {
// Check if user can be asked again or needs to go to settings
if (!permissionResult.canAskAgain) {
showToast('error', t('callImages.permission_denied_settings'));
} else {
showToast('error', t('common.permission_denied'));
}
return;
}
}

const result = await ImagePicker.launchCameraAsync({
mediaTypes: ['images'],
// allowsEditing is only supported on iOS and Android, not on Web
allowsEditing: Platform.OS !== 'web',
quality: 0.8,
});

// Handle Android MainActivity destruction - check for pending results
if (Platform.OS === 'android' && result.canceled) {
const pendingResult = await ImagePicker.getPendingResultAsync();
if (pendingResult && 'assets' in pendingResult && pendingResult.assets && pendingResult.assets.length > 0) {
const asset = pendingResult.assets[0];
if (asset?.uri) {
const filename = `camera_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
return;
}
}
}

if (!result.canceled && result.assets && result.assets.length > 0) {
const asset = result.assets[0];
if (asset?.uri) {
const filename = `camera_${Date.now()}.png`;
setSelectedImageInfo({ uri: asset.uri, filename });
} else {
console.error('Camera returned asset without URI');
showToast('error', t('callImages.capture_error'));
}
}
} catch (error) {
console.error('Error capturing image from camera:', error);
showToast('error', t('callImages.capture_error'));
}
};

const handleUploadImage = async () => {
if (!selectedImageInfo) return;

// Validate URI before processing
if (!selectedImageInfo.uri || typeof selectedImageInfo.uri !== 'string') {
console.error('Invalid image URI:', selectedImageInfo.uri);
showToast('error', t('callImages.upload_error'));
return;
}

setIsUploading(true);
try {
// Manipulate image to ensure PNG format and proper compression
const manipulatedImage = await ImageManipulator.manipulateAsync(
selectedImageInfo.uri,
[{ resize: { width: 1024 } }], // Resize to max width of 1024px while maintaining aspect ratio
{
compress: 0.8,
format: ImageManipulator.SaveFormat.PNG, // Ensure PNG format
let base64Image: string;

// On Web, we can skip image manipulation if there are issues
// On native platforms, manipulate the image for consistency
if (Platform.OS === 'web') {
// On Web, try to manipulate but have a fallback
try {
const manipulatedImage = await ImageManipulator.manipulateAsync(

Check warning on line 264 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `⏎············selectedImageInfo.uri,⏎············[{·resize:·{·width:·1024·}·}],⏎···········` with `selectedImageInfo.uri,·[{·resize:·{·width:·1024·}·}],`
selectedImageInfo.uri,
[{ resize: { width: 1024 } }],
{
compress: 0.8,

Check warning on line 268 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
format: ImageManipulator.SaveFormat.JPEG,

Check warning on line 269 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
base64: true, // Get base64 directly to avoid FileSystem issues on web

Check warning on line 270 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `··············` with `············`
}

Check warning on line 271 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `··}⏎··········` with `}`
);

if (manipulatedImage?.base64) {
base64Image = manipulatedImage.base64;
} else {
// Fallback: try to read from data URI if manipulation didn't return base64
throw new Error('No base64 returned from manipulation');
}
} catch (webError) {
console.error('Web image manipulation error:', webError);
// On web, the URI might already be a data URI or blob URL
// Try to extract base64 from data URI
if (selectedImageInfo.uri.startsWith('data:')) {
const base64Match = selectedImageInfo.uri.match(/base64,(.*)$/);
if (base64Match && base64Match[1]) {
base64Image = base64Match[1];
} else {
throw new Error('Could not extract base64 from data URI');
}
} else {
throw webError;
}
}
} else {
// Native platforms (iOS/Android)
let manipulatedImage;
try {
manipulatedImage = await ImageManipulator.manipulateAsync(

Check warning on line 299 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `⏎············selectedImageInfo.uri,⏎············[{·resize:·{·width:·1024·}·}],⏎···········` with `selectedImageInfo.uri,·[{·resize:·{·width:·1024·}·}],`
selectedImageInfo.uri,
[{ resize: { width: 1024 } }],
{
compress: 0.8,

Check warning on line 303 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
format: ImageManipulator.SaveFormat.PNG,

Check warning on line 304 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
base64: true, // Get base64 directly

Check warning on line 305 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `··············` with `············`
}

Check warning on line 306 in src/components/calls/call-images-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Replace `··}⏎··········` with `}`
);
} catch (manipulateError) {
console.error('Error manipulating image:', manipulateError);
// Try without resize as fallback for problematic images
manipulatedImage = await ImageManipulator.manipulateAsync(
selectedImageInfo.uri,
[], // No transformations
{
compress: 0.8,
format: ImageManipulator.SaveFormat.JPEG,
base64: true,
}
);
}
);

// Read the manipulated image as base64
const base64Image = await FileSystem.readAsStringAsync(manipulatedImage.uri, {
encoding: FileSystem.EncodingType.Base64,
});
if (manipulatedImage?.base64) {
base64Image = manipulatedImage.base64;
} else if (manipulatedImage?.uri) {
// Fallback to FileSystem if base64 wasn't returned
base64Image = await FileSystem.readAsStringAsync(manipulatedImage.uri, {
encoding: FileSystem.EncodingType.Base64,
});
} else {
throw new Error('Image manipulation failed - no output');
}
}

// Get current location if available
const currentLatitude = latitude;
Expand Down
Loading
Loading