Skip to content

Commit

Permalink
feat(achievements): achievements frontend
Browse files Browse the repository at this point in the history
Implement achievements frontend. Need validation
Refs polito#549
  • Loading branch information
domenicoMuscill0 committed Dec 16, 2024
1 parent 9e46cfb commit a7f3228
Show file tree
Hide file tree
Showing 7 changed files with 590 additions and 0 deletions.
3 changes: 3 additions & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,9 @@
"globalTitle": "Global",
"globalSubtitle": "Important notifications are always active"
},
"achievementsScreen": {
"title": "Achievements"
},
"offeringBachelorScreen": {
"title": "Bachelor degree"
},
Expand Down
3 changes: 3 additions & 0 deletions assets/translations/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,9 @@
"globalTitle": "Globali",
"globalSubtitle": "Le notifiche importanti sono sempre attive"
},
"achievementsScreen": {
"title": "Obiettivi"
},
"offeringBachelorScreen": {
"title": "Lauree triennali"
},
Expand Down
146 changes: 146 additions & 0 deletions src/features/user/components/AddAchievementsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useState } from 'react';
import {
Alert,
Button,
Image,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';

// import ImagePicker from 'react-native-image-crop-picker';
import { Achievement } from '../screens/AchievementsScreen';

interface AchievementFormProps {
onSave: (achievement: Achievement, titleName: string) => void;
titles: string[];
}

export const AddAchievementForm: React.FC<AchievementFormProps> = ({
onSave,
titles,
}) => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [image, setImage] = useState('');
const [selectedTitle, setSelectedTitle] = useState(titles[0]);
const [isDropdownVisible, setIsDropdownVisible] = useState(false);

// const pickImage = () => {
// ImagePicker.openPicker({
// width: 300,
// height: 400,
// cropping: true,
// })
// .then(img => {
// setImage(img.path);
// })
// .catch(error => {
// // TODO logger error?
// });
// };

const handleSave = () => {
if (!title.trim()) {
Alert.alert('Validation Error', 'Title is required.');
return;
}
if (!description.trim()) {
Alert.alert('Validation Error', 'Description is required.');
return;
}

const newAchievement: Achievement = {
title,
description,
achieved: false,
image,
};
onSave(newAchievement, selectedTitle);
// Reset the form
setTitle('');
setDescription('');
setImage('');
setSelectedTitle(titles[0]);
};

const handleDropdownToggle = () => {
setIsDropdownVisible(!isDropdownVisible);
};

const handleSelectTitle = (t: string) => {
setSelectedTitle(t);
setIsDropdownVisible(false);
};

return (
<View style={styles.container}>
<TextInput
placeholder="Title"
value={title}
onChangeText={setTitle}
style={styles.input}
/>
<TextInput
placeholder="Description"
value={description}
onChangeText={setDescription}
style={styles.input}
/>
<TouchableOpacity onPress={handleDropdownToggle} style={styles.dropdown}>
<Text>{selectedTitle}</Text>
</TouchableOpacity>
{isDropdownVisible && (
<ScrollView style={styles.dropdownMenu}>
{titles.map((item, index) => (
<TouchableOpacity
key={index}
onPress={() => handleSelectTitle(item)}
style={styles.dropdownItem}
>
<Text>{item}</Text>
</TouchableOpacity>
))}
</ScrollView>
)}
{/* <Button title="Pick an Image" onPress={pickImage} />*/}
{image && <Image source={{ uri: image }} style={styles.image} />}
<Button title="Save Achievement" onPress={handleSave} />
</View>
);
};

const styles = StyleSheet.create({
container: {
padding: 16,
},
input: {
borderBottomWidth: 1,
marginBottom: 16,
},
image: {
width: 200,
height: 200,
marginVertical: 16,
},
dropdown: {
padding: 12,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 4,
marginBottom: 16,
},
dropdownMenu: {
maxHeight: 150,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 4,
marginBottom: 16,
},
dropdownItem: {
padding: 12,
},
});
93 changes: 93 additions & 0 deletions src/features/user/components/ShineAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useEffect, useRef } from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import Svg, { Circle, Rect } from 'react-native-svg';

const getRandomInt = max => Math.floor(Math.random() * max);

export const ShineAnimation = ({
speedFactor = 0.05,
starColor = [255, 255, 255],
starCount = 5000,
}) => {
const { width, height } = Dimensions.get('window');
const starsRef = useRef([]);

useEffect(() => {
const createStars = count => {
const stars = [];
for (let i = 0; i < count; i++) {
stars.push({
id: i,
x: getRandomInt(1600) - 800,
y: getRandomInt(900) - 450,
z: getRandomInt(1000),
});
}
return stars;
};

starsRef.current = createStars(starCount);

const moveStars = (distance: number) => {
starsRef.current.forEach(star => {
star.z -= distance;
if (star.z <= 1) star.z += 1000;
});
};

let prevTime = Date.now();

const tick = () => {
const now = Date.now();
const elapsed = now - prevTime;
prevTime = now;

moveStars(elapsed * speedFactor);
requestAnimationFrame(tick);
};

requestAnimationFrame(tick);
}, [speedFactor, starCount]);

const renderStars = () => {
const cx = width / 2;
const cy = height / 2;
const starElements = starsRef.current.map(star => {
const x = cx + star.x / (star.z * 0.001);
const y = cy + star.y / (star.z * 0.001);
const d = star.z / 1000.0;
const b = 1 - d * d;
if (x < 0 || x >= width || y < 0 || y >= height) return null;
return (
<Circle
key={star.id}
cx={x}
cy={y}
r="1"
fill={`rgba(${starColor[0]},${starColor[1]},${starColor[2]},${b})`}
/>
);
});
return starElements;
};

return (
<View style={styles.container}>
<Svg height="100%" width="100%">
<Rect x="0" y="0" width="100%" height="100%" fillOpacity={0} />
{renderStars()}
</Svg>
</View>
);
};

const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 10,
},
});
8 changes: 8 additions & 0 deletions src/features/user/components/UserNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useTitlesStyles } from '../../../core/hooks/useTitlesStyles';
import { SharedScreens } from '../../../shared/navigation/SharedScreens';
import { DegreeTopTabsNavigator } from '../../offering/navigation/DegreeTopTabsNavigator';
import { OfferingStackParamList } from '../../services/components/ServicesNavigator';
import { AchievementsScreen } from '../screens/AchievementsScreen';
import { MessageScreen } from '../screens/MessageScreen';
import { MessagesScreen } from '../screens/MessagesScreen';
import { NotificationsScreen } from '../screens/NotificationsScreen';
Expand Down Expand Up @@ -54,6 +55,13 @@ export const UserNavigator = () => {
headerTitle: t('profileScreen.title'),
}}
/>
<Stack.Screen
name="Achievements"
component={AchievementsScreen}
options={{
headerTitle: t('achievementsScreen.title'),
}}
/>
<Stack.Screen
name="Notifications"
component={NotificationsScreen}
Expand Down
Loading

0 comments on commit a7f3228

Please sign in to comment.