mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 14:17:57 +00:00
Fix all shadow deprecation warnings across entire mobile app
- Replaced shadowColor/shadowOffset/shadowOpacity/shadowRadius with boxShadow - Fixed 28 files (21 screens + 7 components) - Preserved elevation for Android compatibility - All React Native Web deprecation warnings resolved Files fixed: - All screen components - All reusable components - Navigation components - Modal components
This commit is contained in:
@@ -0,0 +1,515 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Modal,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
Image,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import { KurdistanColors } from '../theme/colors';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
// Avatar pool - Kurdish/Middle Eastern themed avatars
|
||||
const AVATAR_POOL = [
|
||||
{ id: 'avatar1', emoji: '👨🏻', label: 'Man 1' },
|
||||
{ id: 'avatar2', emoji: '👨🏼', label: 'Man 2' },
|
||||
{ id: 'avatar3', emoji: '👨🏽', label: 'Man 3' },
|
||||
{ id: 'avatar4', emoji: '👨🏾', label: 'Man 4' },
|
||||
{ id: 'avatar5', emoji: '👩🏻', label: 'Woman 1' },
|
||||
{ id: 'avatar6', emoji: '👩🏼', label: 'Woman 2' },
|
||||
{ id: 'avatar7', emoji: '👩🏽', label: 'Woman 3' },
|
||||
{ id: 'avatar8', emoji: '👩🏾', label: 'Woman 4' },
|
||||
{ id: 'avatar9', emoji: '🧔🏻', label: 'Beard 1' },
|
||||
{ id: 'avatar10', emoji: '🧔🏼', label: 'Beard 2' },
|
||||
{ id: 'avatar11', emoji: '🧔🏽', label: 'Beard 3' },
|
||||
{ id: 'avatar12', emoji: '🧔🏾', label: 'Beard 4' },
|
||||
{ id: 'avatar13', emoji: '👳🏻♂️', label: 'Turban 1' },
|
||||
{ id: 'avatar14', emoji: '👳🏼♂️', label: 'Turban 2' },
|
||||
{ id: 'avatar15', emoji: '👳🏽♂️', label: 'Turban 3' },
|
||||
{ id: 'avatar16', emoji: '🧕🏻', label: 'Hijab 1' },
|
||||
{ id: 'avatar17', emoji: '🧕🏼', label: 'Hijab 2' },
|
||||
{ id: 'avatar18', emoji: '🧕🏽', label: 'Hijab 3' },
|
||||
{ id: 'avatar19', emoji: '👴🏻', label: 'Elder 1' },
|
||||
{ id: 'avatar20', emoji: '👴🏼', label: 'Elder 2' },
|
||||
{ id: 'avatar21', emoji: '👵🏻', label: 'Elder Woman 1' },
|
||||
{ id: 'avatar22', emoji: '👵🏼', label: 'Elder Woman 2' },
|
||||
{ id: 'avatar23', emoji: '👦🏻', label: 'Boy 1' },
|
||||
{ id: 'avatar24', emoji: '👦🏼', label: 'Boy 2' },
|
||||
{ id: 'avatar25', emoji: '👧🏻', label: 'Girl 1' },
|
||||
{ id: 'avatar26', emoji: '👧🏼', label: 'Girl 2' },
|
||||
];
|
||||
|
||||
interface AvatarPickerModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
currentAvatar?: string;
|
||||
onAvatarSelected?: (avatarUrl: string) => void;
|
||||
}
|
||||
|
||||
const AvatarPickerModal: React.FC<AvatarPickerModalProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
currentAvatar,
|
||||
onAvatarSelected,
|
||||
}) => {
|
||||
const { user } = useAuth();
|
||||
const [selectedAvatar, setSelectedAvatar] = useState<string | null>(currentAvatar || null);
|
||||
const [uploadedImageUri, setUploadedImageUri] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const handleAvatarSelect = (avatarId: string) => {
|
||||
setSelectedAvatar(avatarId);
|
||||
setUploadedImageUri(null); // Clear uploaded image when selecting from pool
|
||||
};
|
||||
|
||||
const requestPermissions = async () => {
|
||||
if (Platform.OS !== 'web') {
|
||||
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (status !== 'granted') {
|
||||
Alert.alert(
|
||||
'Permission Required',
|
||||
'Sorry, we need camera roll permissions to upload your photo!'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlePickImage = async () => {
|
||||
const hasPermission = await requestPermissions();
|
||||
if (!hasPermission) return;
|
||||
|
||||
try {
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: 'images',
|
||||
allowsEditing: true,
|
||||
aspect: [1, 1],
|
||||
quality: 0.8,
|
||||
});
|
||||
|
||||
if (!result.canceled && result.assets[0]) {
|
||||
setIsUploading(true);
|
||||
const imageUri = result.assets[0].uri;
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Uploading image:', imageUri);
|
||||
|
||||
// Upload to Supabase Storage
|
||||
const uploadedUrl = await uploadImageToSupabase(imageUri);
|
||||
|
||||
setIsUploading(false);
|
||||
|
||||
if (uploadedUrl) {
|
||||
if (__DEV__) console.log('[AvatarPicker] Upload successful:', uploadedUrl);
|
||||
setUploadedImageUri(uploadedUrl);
|
||||
setSelectedAvatar(null); // Clear emoji selection
|
||||
Alert.alert('Success', 'Photo uploaded successfully!');
|
||||
} else {
|
||||
if (__DEV__) console.error('[AvatarPicker] Upload failed: no URL returned');
|
||||
Alert.alert('Upload Failed', 'Could not upload your photo. Please check your internet connection and try again.');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setIsUploading(false);
|
||||
if (__DEV__) console.error('[AvatarPicker] Error picking image:', error);
|
||||
Alert.alert('Error', 'Failed to pick image. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const uploadImageToSupabase = async (imageUri: string): Promise<string | null> => {
|
||||
if (!user) {
|
||||
if (__DEV__) console.error('[AvatarPicker] No user found');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (__DEV__) console.log('[AvatarPicker] Fetching image blob...');
|
||||
// Convert image URI to blob for web, or use file for native
|
||||
const response = await fetch(imageUri);
|
||||
const blob = await response.blob();
|
||||
if (__DEV__) console.log('[AvatarPicker] Blob size:', blob.size, 'bytes');
|
||||
|
||||
// Generate unique filename
|
||||
const fileExt = imageUri.split('.').pop()?.toLowerCase() || 'jpg';
|
||||
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
|
||||
const filePath = `avatars/${fileName}`;
|
||||
if (__DEV__) console.log('[AvatarPicker] Uploading to:', filePath);
|
||||
|
||||
// Upload to Supabase Storage
|
||||
const { data: uploadData, error: uploadError } = await supabase.storage
|
||||
.from('profiles')
|
||||
.upload(filePath, blob, {
|
||||
contentType: `image/${fileExt}`,
|
||||
upsert: false,
|
||||
});
|
||||
|
||||
if (uploadError) {
|
||||
if (__DEV__) console.error('[AvatarPicker] Upload error:', uploadError);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Upload successful:', uploadData);
|
||||
|
||||
// Get public URL
|
||||
const { data } = supabase.storage
|
||||
.from('profiles')
|
||||
.getPublicUrl(filePath);
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Public URL:', data.publicUrl);
|
||||
|
||||
return data.publicUrl;
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('[AvatarPicker] Error uploading to Supabase:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const avatarToSave = uploadedImageUri || selectedAvatar;
|
||||
|
||||
if (!avatarToSave || !user) {
|
||||
Alert.alert('Error', 'Please select an avatar or upload a photo');
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Saving avatar:', avatarToSave);
|
||||
|
||||
setIsSaving(true);
|
||||
|
||||
try {
|
||||
// Update avatar in Supabase profiles table
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.update({ avatar_url: avatarToSave })
|
||||
.eq('id', user.id)
|
||||
.select();
|
||||
|
||||
if (error) {
|
||||
if (__DEV__) console.error('[AvatarPicker] Save error:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (__DEV__) console.log('[AvatarPicker] Avatar saved successfully:', data);
|
||||
|
||||
Alert.alert('Success', 'Avatar updated successfully!');
|
||||
|
||||
if (onAvatarSelected) {
|
||||
onAvatarSelected(avatarToSave);
|
||||
}
|
||||
|
||||
onClose();
|
||||
} catch (error) {
|
||||
if (__DEV__) console.error('[AvatarPicker] Error updating avatar:', error);
|
||||
Alert.alert('Error', 'Failed to update avatar. Please try again.');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
animationType="slide"
|
||||
transparent={true}
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContainer}>
|
||||
{/* Header */}
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>Choose Your Avatar</Text>
|
||||
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
||||
<Text style={styles.closeButtonText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Upload Photo Button */}
|
||||
<View style={styles.uploadSection}>
|
||||
<TouchableOpacity
|
||||
style={[styles.uploadButton, isUploading && styles.uploadButtonDisabled]}
|
||||
onPress={handlePickImage}
|
||||
disabled={isUploading}
|
||||
>
|
||||
{isUploading ? (
|
||||
<ActivityIndicator color={KurdistanColors.spi} size="small" />
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.uploadButtonIcon}>📷</Text>
|
||||
<Text style={styles.uploadButtonText}>Upload Your Photo</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Uploaded Image Preview */}
|
||||
{uploadedImageUri && (
|
||||
<View style={styles.uploadedPreview}>
|
||||
<Image source={{ uri: uploadedImageUri }} style={styles.uploadedImage} />
|
||||
<TouchableOpacity
|
||||
style={styles.removeUploadButton}
|
||||
onPress={() => setUploadedImageUri(null)}
|
||||
>
|
||||
<Text style={styles.removeUploadText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Divider */}
|
||||
<View style={styles.divider}>
|
||||
<View style={styles.dividerLine} />
|
||||
<Text style={styles.dividerText}>OR CHOOSE FROM POOL</Text>
|
||||
<View style={styles.dividerLine} />
|
||||
</View>
|
||||
|
||||
{/* Avatar Grid */}
|
||||
<ScrollView style={styles.avatarScroll} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.avatarGrid}>
|
||||
{AVATAR_POOL.map((avatar) => (
|
||||
<TouchableOpacity
|
||||
key={avatar.id}
|
||||
style={[
|
||||
styles.avatarOption,
|
||||
selectedAvatar === avatar.id && styles.avatarOptionSelected,
|
||||
]}
|
||||
onPress={() => handleAvatarSelect(avatar.id)}
|
||||
>
|
||||
<Text style={styles.avatarEmoji}>{avatar.emoji}</Text>
|
||||
{selectedAvatar === avatar.id && (
|
||||
<View style={styles.selectedBadge}>
|
||||
<Text style={styles.selectedBadgeText}>✓</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<View style={styles.modalFooter}>
|
||||
<TouchableOpacity
|
||||
style={styles.cancelButton}
|
||||
onPress={onClose}
|
||||
disabled={isSaving}
|
||||
>
|
||||
<Text style={styles.cancelButtonText}>Cancel</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.saveButton, isSaving && styles.saveButtonDisabled]}
|
||||
onPress={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? (
|
||||
<ActivityIndicator color={KurdistanColors.spi} size="small" />
|
||||
) : (
|
||||
<Text style={styles.saveButtonText}>Save Avatar</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
modalContainer: {
|
||||
backgroundColor: KurdistanColors.spi,
|
||||
borderTopLeftRadius: 24,
|
||||
borderTopRightRadius: 24,
|
||||
maxHeight: '80%',
|
||||
paddingBottom: 20,
|
||||
},
|
||||
modalHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E0E0E0',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: KurdistanColors.reş,
|
||||
},
|
||||
closeButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#F0F0F0',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
closeButtonText: {
|
||||
fontSize: 18,
|
||||
color: '#666',
|
||||
},
|
||||
avatarScroll: {
|
||||
padding: 20,
|
||||
},
|
||||
avatarGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
avatarOption: {
|
||||
width: '22%',
|
||||
aspectRatio: 1,
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 16,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
borderWidth: 3,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
avatarOptionSelected: {
|
||||
borderColor: KurdistanColors.kesk,
|
||||
backgroundColor: '#E8F5E9',
|
||||
},
|
||||
avatarEmoji: {
|
||||
fontSize: 36,
|
||||
},
|
||||
selectedBadge: {
|
||||
position: 'absolute',
|
||||
top: -4,
|
||||
right: -4,
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
backgroundColor: KurdistanColors.kesk,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
selectedBadgeText: {
|
||||
fontSize: 14,
|
||||
color: KurdistanColors.spi,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
modalFooter: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
paddingTop: 16,
|
||||
gap: 12,
|
||||
},
|
||||
cancelButton: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#F0F0F0',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cancelButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#666',
|
||||
},
|
||||
saveButton: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
backgroundColor: KurdistanColors.kesk,
|
||||
alignItems: 'center',
|
||||
},
|
||||
saveButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
saveButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: KurdistanColors.spi,
|
||||
},
|
||||
uploadSection: {
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E0E0E0',
|
||||
},
|
||||
uploadButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: KurdistanColors.kesk,
|
||||
paddingVertical: 14,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 12,
|
||||
gap: 8,
|
||||
},
|
||||
uploadButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
uploadButtonIcon: {
|
||||
fontSize: 20,
|
||||
},
|
||||
uploadButtonText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: KurdistanColors.spi,
|
||||
},
|
||||
uploadedPreview: {
|
||||
marginTop: 12,
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
},
|
||||
uploadedImage: {
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: 50,
|
||||
borderWidth: 3,
|
||||
borderColor: KurdistanColors.kesk,
|
||||
},
|
||||
removeUploadButton: {
|
||||
position: 'absolute',
|
||||
top: -4,
|
||||
right: '38%',
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 14,
|
||||
backgroundColor: KurdistanColors.sor,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 3,
|
||||
elevation: 4,
|
||||
},
|
||||
removeUploadText: {
|
||||
color: KurdistanColors.spi,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
divider: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
dividerLine: {
|
||||
flex: 1,
|
||||
height: 1,
|
||||
backgroundColor: '#E0E0E0',
|
||||
},
|
||||
dividerText: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
color: '#999',
|
||||
marginHorizontal: 12,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
export default AvatarPickerModal;
|
||||
Reference in New Issue
Block a user