More cleanup. More robust themed compononents
This commit is contained in:
parent
86d1df3558
commit
cfcf118275
@ -25,8 +25,8 @@ const SettingsScreen = () => {
|
||||
>
|
||||
<IconSymbol name="person.fill" size={24} color="#007AFF" style={styles.icon} />
|
||||
<ThemedView style={styles.settingContent}>
|
||||
<ThemedText style={styles.settingTitle}>Profile</ThemedText>
|
||||
<ThemedText style={styles.settingSubtitle}>Name, photo, email</ThemedText>
|
||||
<ThemedText style={styles.settingTitle}>Profile Settings</ThemedText>
|
||||
<ThemedText style={styles.settingSubtitle}>Update profile information or sign out.</ThemedText>
|
||||
</ThemedView>
|
||||
<IconSymbol name="chevron.right" size={20} color="#C7C7CC" />
|
||||
</TouchableOpacity>
|
||||
@ -59,7 +59,7 @@ const styles = StyleSheet.create({
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
section: {
|
||||
marginVertical: 16,
|
||||
marginVertical: 8,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
|
@ -1,153 +1,177 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { StyleSheet, TouchableOpacity, Image, Alert, ActivityIndicator } from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import { StyleSheet, Alert, ActivityIndicator, ScrollView } from 'react-native';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import Avatar from '@/components/auth/Profile_Avatar';
|
||||
import { Session } from '@supabase/supabase-js'
|
||||
import Logout_Button from '@/components/auth/Logout_Button';
|
||||
import ProfileAvatar from '@/components/auth/Profile_Avatar';
|
||||
import LogoutButton from '@/components/auth/Logout_Button';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
|
||||
export default function ProfileScreen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [fullName, setFullName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [avatar, setAvatar] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserProfile();
|
||||
}, []);
|
||||
const ProfileScreen = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [profile, setProfile] = useState({
|
||||
full_name: '',
|
||||
email: '',
|
||||
avatar_url: null,
|
||||
provider: ''
|
||||
});
|
||||
|
||||
// Fetch profile when screen comes into focus
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
fetchUserProfile();
|
||||
}, [])
|
||||
);
|
||||
|
||||
const fetchUserProfile = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (user) {
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (data) {
|
||||
setFullName(data.full_name || '');
|
||||
setEmail(data.email || '');
|
||||
setAvatar(data.avatar_url || null);
|
||||
}
|
||||
if (!user) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (data) {
|
||||
setProfile({
|
||||
full_name: data.full_name || '',
|
||||
email: data.email || '',
|
||||
avatar_url: data.avatar_url,
|
||||
provider: data.provider || ''
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching profile:', error);
|
||||
Alert.alert('Error', 'Failed to load profile information');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const updateProfile = async () => {
|
||||
setLoading(true);
|
||||
setUpdating(true);
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!user) throw new Error('User not found');
|
||||
if (!user) throw new Error('Not authenticated');
|
||||
|
||||
// Validate input
|
||||
if (!profile.full_name.trim()) {
|
||||
Alert.alert('Error', 'Please enter your full name');
|
||||
return;
|
||||
}
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
full_name: fullName,
|
||||
email,
|
||||
full_name: profile.full_name.trim(),
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.upsert(updates);
|
||||
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
Alert.alert('Success', 'Profile updated successfully!');
|
||||
} catch (error) {
|
||||
Alert.alert('Error updating profile', error.message);
|
||||
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to update profile');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Add image picking functionality here (similar to previous example)
|
||||
const handleAvatarUpload = () => {
|
||||
// Refresh profile data after avatar upload
|
||||
fetchUserProfile();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ThemedView style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" />
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
|
||||
<Avatar
|
||||
size={50}
|
||||
url={avatar}
|
||||
onUpload={updateProfile}
|
||||
/>
|
||||
|
||||
<ThemedView style={styles.formSection}>
|
||||
<ThemedText style={styles.label}>Full Name</ThemedText>
|
||||
<ThemedTextInput
|
||||
value={fullName}
|
||||
onChangeText={setFullName}
|
||||
placeholder="Enter your full name"
|
||||
style={styles.input}
|
||||
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
||||
<ThemedView style={styles.container}>
|
||||
<ProfileAvatar
|
||||
url={profile.avatar_url}
|
||||
size={120}
|
||||
onUpload={handleAvatarUpload}
|
||||
disabled={updating}
|
||||
/>
|
||||
|
||||
{profile.provider && (
|
||||
<ThemedText style={styles.providerText}>
|
||||
Signed in with {profile.provider.charAt(0).toUpperCase() + profile.provider.slice(1)}
|
||||
</ThemedText>
|
||||
)}
|
||||
|
||||
<ThemedView style={styles.formSection}>
|
||||
<ThemedText type='title' style={styles.label}>Name</ThemedText>
|
||||
<ThemedTextInput
|
||||
value={profile.full_name}
|
||||
onChangeText={(text) => setProfile(prev => ({ ...prev, full_name: text }))}
|
||||
placeholder="Enter your full name"
|
||||
style={styles.input}
|
||||
editable={!updating}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedTextButton
|
||||
text={updating ? 'Saving...' : 'Save Changes'}
|
||||
onPress={updateProfile}
|
||||
disabled={updating || !profile.full_name.trim()}
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.saveButton}
|
||||
/>
|
||||
|
||||
<LogoutButton
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.logoutButton}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedTextButton
|
||||
text='Save Changes'
|
||||
onPress={updateProfile}
|
||||
disabled={loading}
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.saveButton}
|
||||
/>
|
||||
<Logout_Button
|
||||
fontSize={18}
|
||||
fontWeight='semibold'
|
||||
width='90%'
|
||||
style={styles.logoutButton}
|
||||
/>
|
||||
|
||||
</ThemedView>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default ProfileScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollContainer: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
alignItems: 'center',
|
||||
},
|
||||
avatarContainer: {
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
marginBottom: 30,
|
||||
},
|
||||
avatar: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
},
|
||||
avatarPlaceholder: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: '#E1E1E1',
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
changePhotoText: {
|
||||
marginTop: 8,
|
||||
color: '#007AFF',
|
||||
fontSize: 16,
|
||||
},
|
||||
formSection: {
|
||||
marginBottom: 30,
|
||||
marginBottom: 20,
|
||||
},
|
||||
label: {
|
||||
marginBottom: 8,
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
},
|
||||
input: {
|
||||
fontSize: 16,
|
||||
@ -155,15 +179,24 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 10,
|
||||
borderRadius: 8,
|
||||
marginBottom: 20,
|
||||
width: '100%',
|
||||
},
|
||||
disabledInput: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
saveButton: {
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
marginBottom: 10,
|
||||
},
|
||||
logoutButton: {
|
||||
backgroundColor: 'red',
|
||||
marginTop: 50,
|
||||
marginTop: 30,
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
providerText: {
|
||||
marginBottom: 20,
|
||||
fontSize: 14,
|
||||
opacity: 0.7,
|
||||
}
|
||||
});
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { ThemedView, ThemedText, ThemedTextButton, ThemedTextInput } from '@/components/theme';
|
||||
import { Alert, StyleSheet, AppState } from 'react-native';
|
||||
import { ThemedTextButton } from '@/components/theme';
|
||||
import { Alert, StyleSheet } from 'react-native';
|
||||
import React from 'react';
|
||||
import { TextStyle, PressableProps, DimensionValue } from 'react-native';
|
||||
import ThemedButton from '@/components/theme/buttons/ThemedButton';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
|
||||
// Extend ThemedButton props (which already extends PressableProps)
|
||||
type ThemedTextButtonProps = Omit<PressableProps, 'children'> & {
|
||||
@ -38,6 +35,8 @@ const Logout_Button: React.FC<ThemedTextButtonProps> = ({
|
||||
text='Logout'
|
||||
width={width}
|
||||
height={height}
|
||||
textColor='white'
|
||||
backgroundColor='red'
|
||||
fontSize={fontSize}
|
||||
fontWeight={fontWeight}
|
||||
containerStyle={containerStyle}
|
||||
@ -48,5 +47,3 @@ const Logout_Button: React.FC<ThemedTextButtonProps> = ({
|
||||
);
|
||||
};
|
||||
export default Logout_Button;
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
|
@ -1,124 +1,158 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import { StyleSheet, Alert, Image, TouchableOpacity } from 'react-native'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
//import { ImageManipulator } from 'expo-image-manipulator';
|
||||
import { ThemedView, ThemedText, ThemedTextButton } from '../theme';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { StyleSheet, Alert, Image, TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator';
|
||||
import { ThemedView, ThemedText } from '../theme';
|
||||
import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
|
||||
interface Props {
|
||||
size: number
|
||||
url: string | null
|
||||
onUpload: (filePath: string) => void
|
||||
interface AvatarProps {
|
||||
size?: number;
|
||||
url: string | null;
|
||||
onUpload?: (filePath: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
|
||||
const avatarSize = { height: size, width: size }
|
||||
|
||||
export default function ProfileAvatar({
|
||||
url,
|
||||
size = 120,
|
||||
onUpload,
|
||||
disabled = false
|
||||
}: AvatarProps) {
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
if (url) downloadImage(url);
|
||||
}, [url]);
|
||||
|
||||
async function downloadImage(path: string) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const fr = new FileReader()
|
||||
fr.readAsDataURL(data)
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
const fr = new FileReader();
|
||||
fr.readAsDataURL(data);
|
||||
fr.onload = () => {
|
||||
setAvatarUrl(fr.result as string)
|
||||
}
|
||||
setAvatarUrl(fr.result as string);
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
}
|
||||
console.log('Error downloading image: ', error instanceof Error ? error.message : error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadAvatar() {
|
||||
if (disabled || uploading) return;
|
||||
|
||||
try {
|
||||
setUploading(true)
|
||||
setUploading(true);
|
||||
|
||||
// Get current user
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images, // Restrict to only images
|
||||
allowsMultipleSelection: false, // Can only select one image
|
||||
allowsEditing: true, // Allows the user to crop / rotate their photo before uploading it
|
||||
quality: 1,
|
||||
exif: false, // We don't want nor need that data.
|
||||
});
|
||||
|
||||
if (result.canceled || !result.assets || result.assets.length === 0) {
|
||||
console.log('User cancelled image picker.');
|
||||
if (!user) throw new Error('User not authenticated');
|
||||
|
||||
// Request permission if needed
|
||||
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
if (!permissionResult.granted) {
|
||||
Alert.alert('Permission Required', 'Please allow access to your photo library to upload an avatar.');
|
||||
return;
|
||||
}
|
||||
|
||||
//const manipulatedImage = await ImageManipulator.manipulate(result.assets[0].uri)
|
||||
//.resize({ width: 300 })
|
||||
//.renderAsync();
|
||||
|
||||
//const manipulateResult = await manipulateAsync(
|
||||
//result.assets[0].uri,
|
||||
//[{resize: {width: 300, height: 300}}],
|
||||
//{compress: 0.7, format: SaveFormat.JPEG}
|
||||
//);
|
||||
|
||||
const image = result.assets[0];
|
||||
console.log('Got image', image)
|
||||
|
||||
if (!image.uri) {
|
||||
throw new Error('No image uri!') // Realistically, this should never happen, but just in case...
|
||||
|
||||
// Launch image picker
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||
allowsMultipleSelection: false,
|
||||
allowsEditing: true,
|
||||
aspect: [1, 1],
|
||||
quality: 0.8,
|
||||
exif: false,
|
||||
});
|
||||
|
||||
if (result.canceled || !result.assets || result.assets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const arraybuffer = await fetch(image.uri).then((res) => res.arrayBuffer())
|
||||
|
||||
const fileExt = image.uri?.split('.').pop()?.toLowerCase() ?? 'jpeg'
|
||||
const path = `${Date.now()}.${fileExt}`
|
||||
|
||||
const image = result.assets[0];
|
||||
|
||||
// Compress and resize the image
|
||||
const compressedImage = await manipulateAsync(
|
||||
image.uri,
|
||||
[{ resize: { width: 300, height: 300 } }],
|
||||
{ compress: 0.7, format: SaveFormat.JPEG }
|
||||
);
|
||||
|
||||
// Get file info to check size
|
||||
const fileInfo = await FileSystem.getInfoAsync(compressedImage.uri);
|
||||
|
||||
// Convert to array buffer for upload
|
||||
const arraybuffer = await fetch(compressedImage.uri).then((res) => res.arrayBuffer());
|
||||
|
||||
// Generate unique filename
|
||||
const fileExt = compressedImage.uri.split('.').pop()?.toLowerCase() ?? 'jpg';
|
||||
const fileName = `${user.id}_${Date.now()}.${fileExt}`;
|
||||
|
||||
// Upload to Supabase Storage
|
||||
const { data, error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(path, arraybuffer, {
|
||||
contentType: image.mimeType ?? 'image/jpeg',
|
||||
.upload(fileName, arraybuffer, {
|
||||
contentType: `image/${fileExt}`,
|
||||
upsert: true,
|
||||
});
|
||||
const { data: updateData, error: updateError } = await supabase
|
||||
|
||||
if (uploadError) throw uploadError;
|
||||
|
||||
// Update user profile with new avatar URL
|
||||
const { error: updateError } = await supabase
|
||||
.from('profiles')
|
||||
.update({ avatar_url: data?.path })
|
||||
.eq('id', user?.id);
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(data.path)
|
||||
.update({
|
||||
avatar_url: data.path,
|
||||
updated_at: new Date()
|
||||
})
|
||||
.eq('id', user.id);
|
||||
|
||||
if (updateError) throw updateError;
|
||||
|
||||
// Set the new avatar URL
|
||||
setAvatarUrl(compressedImage.uri);
|
||||
|
||||
// Call the onUpload callback if provided
|
||||
if (onUpload) onUpload(data.path);
|
||||
|
||||
Alert.alert('Success', 'Avatar updated successfully!');
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
Alert.alert(error.message)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
Alert.alert('Error uploading avatar', error instanceof Error ? error.message : 'An unknown error occurred');
|
||||
} finally {
|
||||
setUploading(false)
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={uploadAvatar}
|
||||
style={styles.avatarContainer}
|
||||
style={[styles.avatarContainer, { opacity: disabled ? 0.7 : 1 }]}
|
||||
disabled={disabled || uploading}
|
||||
>
|
||||
{avatarUrl ? (
|
||||
<Image source={{ uri: avatarUrl }} style={styles.avatar} />
|
||||
<Image
|
||||
source={{ uri: avatarUrl }}
|
||||
style={[styles.avatar, { width: size, height: size, borderRadius: size / 2 }]}
|
||||
/>
|
||||
) : (
|
||||
<ThemedView style={styles.avatarPlaceholder}>
|
||||
<IconSymbol name="person.fill" size={50} color="#999" />
|
||||
<ThemedView style={[styles.avatarPlaceholder, { width: size, height: size, borderRadius: size / 2 }]}>
|
||||
<IconSymbol name="person.fill" size={size / 2.5} color="#999" />
|
||||
</ThemedView>
|
||||
)}
|
||||
<ThemedText style={styles.changePhotoText}>Change Photo</ThemedText>
|
||||
|
||||
{uploading ? (
|
||||
<ActivityIndicator style={styles.uploadingIndicator} size="small" color="#007AFF" />
|
||||
) : (
|
||||
<ThemedText style={styles.changePhotoText}>
|
||||
{disabled ? 'Avatar' : 'Change Photo'}
|
||||
</ThemedText>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@ -126,18 +160,12 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
|
||||
const styles = StyleSheet.create({
|
||||
avatarContainer: {
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
marginBottom: 30,
|
||||
marginVertical: 20,
|
||||
},
|
||||
avatar: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: '#E1E1E1',
|
||||
},
|
||||
avatarPlaceholder: {
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: '#E1E1E1',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
@ -147,4 +175,7 @@ const styles = StyleSheet.create({
|
||||
color: '#007AFF',
|
||||
fontSize: 16,
|
||||
},
|
||||
uploadingIndicator: {
|
||||
marginTop: 8,
|
||||
}
|
||||
});
|
||||
|
@ -10,34 +10,34 @@ const DEFAULT_HEIGHT = 68;
|
||||
type ThemedButtonProps = PressableProps & {
|
||||
width?: DimensionValue;
|
||||
height?: DimensionValue;
|
||||
backgroundColor?: string;
|
||||
containerStyle?: object;
|
||||
buttonStyle?: object;
|
||||
};
|
||||
|
||||
const ThemedButton: React.FC<ThemedButtonProps> = ({
|
||||
width,
|
||||
height,
|
||||
width = DEFAULT_WIDTH,
|
||||
height = DEFAULT_HEIGHT,
|
||||
backgroundColor = Colors[useColorScheme() ?? 'dark'].text,
|
||||
children,
|
||||
containerStyle,
|
||||
buttonStyle,
|
||||
style,
|
||||
...restProps // This now includes onPress automatically
|
||||
}) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
|
||||
return (
|
||||
<ThemedView
|
||||
style={[
|
||||
styles.buttonContainer,
|
||||
{
|
||||
width: width ?? DEFAULT_WIDTH,
|
||||
height: height ?? DEFAULT_HEIGHT,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
containerStyle,
|
||||
]}
|
||||
>
|
||||
<Pressable
|
||||
style={[styles.button, { backgroundColor: Colors[scheme].text }, buttonStyle, style]}
|
||||
style={[styles.button, { backgroundColor }, buttonStyle, style]}
|
||||
{...restProps} // This passes onPress and all other Pressable props
|
||||
>
|
||||
{children}
|
||||
|
@ -15,6 +15,8 @@ type ThemedTextButtonProps = Omit<PressableProps, 'children'> & {
|
||||
textStyle?: TextStyle;
|
||||
containerStyle?: object;
|
||||
buttonStyle?: object;
|
||||
textColor?: string;
|
||||
backgroundColor?: string;
|
||||
};
|
||||
|
||||
const ThemedTextButton: React.FC<ThemedTextButtonProps> = ({
|
||||
@ -26,9 +28,10 @@ const ThemedTextButton: React.FC<ThemedTextButtonProps> = ({
|
||||
textStyle,
|
||||
containerStyle,
|
||||
buttonStyle,
|
||||
textColor = Colors[useColorScheme() ?? 'dark'].background,
|
||||
backgroundColor = Colors[useColorScheme() ?? 'dark'].text,
|
||||
...restProps // This includes onPress and all other Pressable props
|
||||
}) => {
|
||||
const scheme = useColorScheme() ?? 'dark';
|
||||
if (fontWeight === 'semibold') fontWeight = '600';
|
||||
|
||||
return (
|
||||
@ -37,12 +40,13 @@ const ThemedTextButton: React.FC<ThemedTextButtonProps> = ({
|
||||
height={height}
|
||||
containerStyle={containerStyle}
|
||||
buttonStyle={buttonStyle}
|
||||
backgroundColor={backgroundColor}
|
||||
{...restProps}
|
||||
>
|
||||
<ThemedText
|
||||
style={[
|
||||
{
|
||||
color: Colors[scheme].background,
|
||||
color: textColor,
|
||||
fontSize,
|
||||
lineHeight: fontSize * 1.5,
|
||||
fontWeight,
|
||||
|
Loading…
x
Reference in New Issue
Block a user