themed style

This commit is contained in:
Jaanu423
2025-09-26 18:08:48 +05:30
parent 87872f1df1
commit 3f49c28371
8 changed files with 311 additions and 121 deletions
+64
View File
@@ -0,0 +1,64 @@
import React, { useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withRepeat,
withSequence,
withDelay,
Easing,
} from 'react-native-reanimated';
const AnimatedText = ({ text, style, delay = 0 }) => {
const opacity = useSharedValue(0);
const translateY = useSharedValue(20);
useEffect(() => {
opacity.value = withDelay(delay, withTiming(1, { duration: 800 }));
translateY.value = withDelay(delay, withTiming(0, { duration: 800 }));
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ translateY: translateY.value }],
}));
return (
<Animated.Text style={[styles.text, style, animatedStyle]}>
{text}
</Animated.Text>
);
};
export default AnimatedText;
// Usage
const MyComponent = () => {
return (
<View style={styles.container}>
<AnimatedText text="Hello World" style={styles.title} />
<AnimatedText text="Welcome to Reanimated" style={styles.subtitle} delay={200} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 32,
fontWeight: 'bold',
marginBottom: 10,
},
subtitle: {
fontSize: 18,
color: '#666',
},
text: {
textAlign: 'center',
},
});
+29 -16
View File
@@ -27,8 +27,6 @@ import { ApiResponse } from "./home/UserProfileStatus";
import * as Keychain from "react-native-keychain";
const { width } = Dimensions.get('window');
// --- Types ---
interface Group {
id: string;
@@ -39,6 +37,7 @@ interface Group {
contributionAmount: number;
cycleType: string;
createdByName: string;
imageBlob: string
}
// --- UI Components ---
@@ -48,16 +47,25 @@ const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (gro
return (
<View style={cardStyle}>
<Text style={styles.groupName} numberOfLines={1}>{item.groupName}</Text>
<Text style={styles.groupDescription} numberOfLines={isGrid ? 2 : 1}>{item.description}</Text>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
<View>
<Text style={styles.groupName} numberOfLines={1}>{item.groupName}</Text>
<Text style={styles.groupDescription} numberOfLines={isGrid ? 2 : 1}>{item.description}</Text>
</View>
<Image
source={{ uri: item?.imageBlob !== null ? `data:image/jpeg;base64${item?.imageBlob}` : `https://i.pravatar.cc/150?u=${item.id}` }}
style={{ width: 50, borderWidth: 1, height: 50, borderColor: colors.pastelGreen, borderRadius: 25 }}
/>
</View>
<View style={styles.groupMeta}>
<Text style={styles.groupMetaText}>{item.currentParticipants}/{item.maxParticipants} Members</Text>
<Text style={styles.groupMetaText}>{item.cycleType}</Text>
</View>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.viewButton} onPress={() => onView(item)}>
{/* <TouchableOpacity style={styles.viewButton} onPress={() => onView(item)}>
<Text style={styles.viewButtonText}>View</Text>
</TouchableOpacity>
</TouchableOpacity> */}
<TouchableOpacity
style={[styles.joinButton, isFull && styles.joinButtonDisabled]}
onPress={() => onJoin(item.id)}
@@ -70,15 +78,19 @@ const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (gro
);
};
const FeaturedGroupCard = ({ item }: { item: Group }) => (
<View style={styles.featuredCard}>
<Image source={{ uri: `https://i.pravatar.cc/150?u=${item.id}` }} style={styles.featuredImage} />
<View style={styles.featuredContent}>
<Text style={styles.featuredName} numberOfLines={1}>{item.groupName}</Text>
<Text style={styles.featuredCreator}>by {item.createdByName}</Text>
const FeaturedGroupCard = ({ item }: { item: Group }) => {
console.log("image item : ", item)
console.log("image view : ", `data:image/jpeg;base64${item?.imageBlob}`)
return (
<View style={styles.featuredCard}>
<Image source={{ uri: item?.imageBlob !== null ? `data:image/jpeg;base64${item?.imageBlob}` : `https://i.pravatar.cc/150?u=${item.id}` }} style={styles.featuredImage} />
<View style={styles.featuredContent}>
<Text style={styles.featuredName} numberOfLines={1}>{item.groupName}</Text>
<Text style={styles.featuredCreator}>by {item.createdByName}</Text>
</View>
</View>
</View>
);
)
};
export default function GroupScreen() {
const [activeTab, setActiveTab] = useState("Public");
@@ -114,6 +126,7 @@ export default function GroupScreen() {
}
};
const fetchJoinedGroups = async () => {
if (loading) return;
setLoading(true);
@@ -166,7 +179,7 @@ export default function GroupScreen() {
Alert.alert("Error", "An unexpected error occurred.");
}
};
const userStatus = async () => {
try {
const credentials = await Keychain.getGenericPassword();
@@ -264,7 +277,7 @@ export default function GroupScreen() {
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
// ListFooterComponent={<ActivityIndicator size={'large'} color={colors.Group} />}
// ListFooterComponent={<ActivityIndicator size={'large'} color={colors.Group} />}
/>
);
};
+2
View File
@@ -19,6 +19,7 @@ import type { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../navigation/AppNavigator";
import { CustomButton, CustomInput } from "../components/common/Custom";
import colors from "../components/colors";
import AnimatedText from "../components/common/AnimatedText";
const theme = {
primary: "#111111",
@@ -94,6 +95,7 @@ return (
{/* Top content */}
<View style={styles.topSection}>
{/* <AnimatedText text="Welcome to Reanimated" style={styles.subtitle} delay={200} /> */}
<Text style={styles.brand}>CircleCollect</Text>
<Text style={styles.title}>Welcome Back 👋</Text>
<Text style={styles.subtitle}>
+113 -68
View File
@@ -58,7 +58,7 @@ export interface GroupDetails {
organizerId: string;
totalParticipants: number;
participants: Participant[];
imageBlob:string
imageBlob: string
}
export interface EditGroupPayload {
@@ -323,82 +323,127 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const isInvitable = groupDetails.status === 'APPROVED';
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
// const pickImage = () => {
// launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
// if (response.didCancel) return;
// if (response.errorCode) {
// Alert.alert("Error", response.errorMessage || "Something went wrong");
// } else if (response.assets && response.assets.length > 0) {
// const selectedImage = response.assets[0];
// console.log(selectedImage?.uri)
// setSelectedImage(selectedImage?.uri)
// }
// });
// };
const pickImage = () => {
launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
if (response.didCancel) return;
if (response.errorCode) {
Alert.alert("Error", response.errorMessage || "Something went wrong");
} else if (response.assets && response.assets.length > 0) {
const selectedImage = response.assets[0];
console.log(selectedImage?.uri)
setSelectedImage(selectedImage?.uri)
return;
}
if (!response.assets || response.assets.length === 0) {
Alert.alert("Error", "No image selected");
return;
}
const selectedImage = response.assets[0];
const maxSizeKB = 1000;
const maxSizeBytes = maxSizeKB * 1024;
// Size validation
if (selectedImage.fileSize && selectedImage.fileSize > maxSizeBytes) {
const currentSizeKB = (selectedImage.fileSize / 1024).toFixed(2);
Alert.alert(
"File Too Large",
`Selected image is ${currentSizeKB}KB. Please select an image smaller than ${maxSizeKB}KB.`
);
return;
}
// Additional validation for required fields
if (!selectedImage.uri) {
Alert.alert("Error", "Invalid image file");
return;
}
console.log("✅ Image selected:", {
uri: selectedImage.uri,
size: (selectedImage.fileSize / 1024).toFixed(2) + "KB",
width: selectedImage.width,
height: selectedImage.height
});
setSelectedImage(selectedImage.uri);
});
};
const handleUpdateImage = async () => {
try {
const credentials = await Keychain.getGenericPassword();
if (!credentials) {
Alert.alert("Error", "You are not logged in");
return;
}
const token = credentials.password;
if (!selectedImage) {
Alert.alert("Error", "Please select an image first");
return;
try {
const credentials = await Keychain.getGenericPassword();
if (!credentials) {
Alert.alert("Error", "You are not logged in");
return;
}
const token = credentials.password;
if (!selectedImage) {
Alert.alert("Error", "Please select an image first");
return;
}
setImageUpdating(true);
// Get file extension and mime type dynamically
const fileExtension = selectedImage.split('.').pop();
const formData = new FormData();
formData.append("file", {
uri: selectedImage,
type: "image/jpeg",
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
});
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
console.log("url : ", url)
console.log("formdata : ", formData)
// return
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${token}`,
},
body: formData,
});
console.log("image response : ", response)
if (response?.status === 200) {
fetchGroupDetails()
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
}
} catch (error: any) {
console.error("❌ Error updating group image:", error);
Alert.alert("Error", error.message || "Could not update group image");
} finally {
setImageUpdating(false);
}
};
setImageUpdating(true);
// Get file extension and mime type dynamically
const fileExtension = selectedImage.split('.').pop();
const formData = new FormData();
formData.append("file", {
uri: selectedImage,
type: "image/jpeg",
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
});
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
console.log("url : ", url)
console.log("formdata : ", formData)
// return
const response = await fetch(url, {
method: "PUT",
headers: {
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${token}`,
},
body: formData,
});
console.log("image response : ", response)
if(response?.status === 200){
fetchGroupDetails()
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
}
} catch (error:any) {
console.error("❌ Error updating group image:", error);
Alert.alert("Error", error.message || "Could not update group image");
} finally {
setImageUpdating(false);
}
};
const getProfileImage = () => {
return selectedImage
? selectedImage
: groupDetails?.imageBlob
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
};
// Helper function to get MIME type
const getProfileImage = () => {
return selectedImage
? selectedImage
: groupDetails?.imageBlob
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
};
// Helper function to get MIME type
return (
@@ -430,12 +475,12 @@ const getProfileImage = () => {
<Ionicons name="camera" size={18} color="#000" />
</TouchableOpacity>
</View>
<View style={{justifyContent:'center'}}>
<View style={{ justifyContent: 'center' }}>
<CustomButton
title={imageUpdating ? "Updating..." : "Update Image"}
onPress={handleUpdateImage}
icon={imageUpdating ? undefined : "cloud-upload"}
style={{paddingHorizontal:20}}
style={{ paddingHorizontal: 20 }}
buttonBackground={colors.pastelBlue}
/>
</View>
@@ -2,7 +2,7 @@ import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { View, Text, StyleSheet, Platform, TouchableOpacity } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Ionicons from 'react-native-vector-icons/Ionicons'; // Correct import for react-native-vector-icons
import {Ionicons} from '@react-native-vector-icons/ionicons'; // Correct import for react-native-vector-icons
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
@@ -28,7 +28,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
console.log("option : ", options)
// A custom option to pass the icon name
const iconName = options.tabBarIconName ?? 'help-circle';
const iconName = label === "Settings" ? "trail-sign-outline" : label === "Payments" ? "trending-up" : label === "Members" ? "people-outline" : "flower";
const isFocused = state.index === index;
@@ -79,7 +79,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
// --- Main Tab Navigator Component ---
const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
const { groupId } = route.params
const { groupId } = route.params
return (
<Tab.Navigator
@@ -97,10 +97,10 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
options={{
tabBarLabel: 'Settings',
// Custom prop to define the icon for our FloatingTabBar
}}
/>
<Tab.Screen
<Tab.Screen
name="Payments"
component={Payments}
// Pass the groupId to the EditGroupScreen as an initial parameter
@@ -111,16 +111,16 @@ const ManagementTabNavigation: React.FC<Props> = ({ route }) => {
}}
/>
<Tab.Screen
<Tab.Screen
name="Member"
component={Member}
// Pass the groupId to the EditGroupScreen as an initial parameter
initialParams={{ groupId }}
initialParams={{ groupId }}
options={{
tabBarLabel: 'Members',
}}
/>
{/* You can easily add more tabs here in the future */}
</Tab.Navigator>