diff --git a/backend/app/migrations/0009_word_sentence.py b/backend/app/migrations/0009_word_sentence.py new file mode 100644 index 00000000..5c96b264 --- /dev/null +++ b/backend/app/migrations/0009_word_sentence.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-11-05 12:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0008_translation_remove_word_categories_delete_category_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='word', + name='sentence', + field=models.CharField(default='Sentence not available', max_length=1000), + ), + ] diff --git a/backend/app/views_directory/create_post-activity-streams.py b/backend/app/views_directory/create_post-activity-streams.py new file mode 100644 index 00000000..b74a8466 --- /dev/null +++ b/backend/app/views_directory/create_post-activity-streams.py @@ -0,0 +1,38 @@ +from rest_framework import status +from rest_framework.decorators import api_view +from rest_framework.response import Response +from ..models import Post, Tags +from ..serializers import PostSerializer + +@api_view(['POST']) +def create_post(request): + post_data = request.data + + serializer = PostSerializer(data=post_data) + if serializer.is_valid(): + post = serializer.save() + + if 'tags' in post_data: + tags = post_data['tags'] + post.tags.add(*[Tags.objects.get_or_create(name=tag)[0] for tag in tags]) + + activity_stream = { + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Create", + "actor": { + "type": "Person", + "name": post.author.username, + }, + "object": { + "type": "Note", + "id": post.id, + "name": post.title, + "content": post.description, + "tags": [{"type": "Hashtag", "name": tag} for tag in post.tags.values_list('name', flat=True)] + }, + "published": post.created_at.isoformat() + } + + return Response({'activity_stream': activity_stream}, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/backend/app/views_directory/like-unlike-views-with-activity-streams.py b/backend/app/views_directory/like-unlike-views-with-activity-streams.py new file mode 100644 index 00000000..252ec62d --- /dev/null +++ b/backend/app/views_directory/like-unlike-views-with-activity-streams.py @@ -0,0 +1,78 @@ +from django.shortcuts import get_object_or_404 +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status +from app.models import Post +import json +from datetime import datetime + +def log_activity(actor, verb, obj_type, obj_id, obj_content): + activity = { + "@context": "localhost", + "type": "Post Like/Unlike", + "actor": { + "type": "Person", + "id": actor.id, + "name": actor.username + }, + "verb": verb, + "object": { + "type": obj_type, + "id": obj_id, + "content": obj_content + }, + "published": datetime.utcnow().isoformat() + 'Z' + } + print(json.dumps(activity)) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def like_post(request): + post_id = request.data.get("post_id") + if not post_id: + return Response({"detail": "Post ID is required."}, status=status.HTTP_400_BAD_REQUEST) + + post = get_object_or_404(Post, id=post_id) + if request.user in post.liked_by.all(): + return Response({"detail": "You have already liked this post."}, status=status.HTTP_400_BAD_REQUEST) + + post.liked_by.add(request.user) + post.like_count = post.liked_by.count() + post.save() + + log_activity( + actor=request.user, + verb="like", + obj_type="Post", + obj_id=post.id, + obj_content=post.content + ) + + return Response({"detail": "Post liked successfully.", "like_count": post.like_count}, status=status.HTTP_200_OK) + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def unlike_post(request): + post_id = request.data.get("post_id") + if not post_id: + return Response({"detail": "Post ID is required."}, status=status.HTTP_400_BAD_REQUEST) + + post = get_object_or_404(Post, id=post_id) + if request.user not in post.liked_by.all(): + return Response({"detail": "You have not liked this post yet."}, status=status.HTTP_400_BAD_REQUEST) + + post.liked_by.remove(request.user) + post.like_count = post.liked_by.count() + post.save() + + # Log the "unlike" activity + log_activity( + actor=request.user, + verb="unlike", + obj_type="Post", + obj_id=post.id, + obj_content=post.content + ) + + return Response({"detail": "Post unliked successfully.", "like_count": post.like_count}, status=status.HTTP_200_OK) diff --git a/backend/app/views_directory/profileviews.py b/backend/app/views_directory/profileviews.py index fb5c433c..b5b30c2a 100644 --- a/backend/app/views_directory/profileviews.py +++ b/backend/app/views_directory/profileviews.py @@ -26,4 +26,125 @@ def update_profile(request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['GET']) +def view_profile_mock(request): + mock_profile = { + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Person", + "id": "https://example.com/profiles/oktay_ozel", + "name": "oktay_ozel", + "image": { + "type": "Image", + "url": "https://private-user-images.githubusercontent.com/24993956/309188090-69afd5bb-8258-4995-939c-4600b6ecce12.jpg" + }, + "followers": { + "type": "Collection", + "totalItems": 100 + }, + "following": { + "type": "Collection", + "totalItems": 50 + }, + "level": "B2", + "activity": [ + { + "type": "Create", + "id": "https://example.com/posts/2", + "actor": { + "type": "Person", + "name": "oktay_ozel", + "image": { + "type": "Image", + "url": "https://private-user-images.githubusercontent.com/57640531/310137517-cbe7aa9f-3457-4f64-b37b-c3e46d4e448b.png" + } + }, + "object": { + "type": "Note", + "name": "Petrichor: The Smell of Rain", + "content": "Petrichor is the pleasant, earthy smell that comes after rain. Example: 'She loved the petrichor that filled the air after the storm.'", + "tag": [ + { + "type": "Hashtag", + "name": "Vocabulary" + } + ], + "published": "2024-11-05T17:00:00Z", + "replies": { + "type": "Collection", + "totalItems": 3 + }, + "likes": { + "type": "Collection", + "totalItems": 90 + } + } + }, + { + "type": "Create", + "id": "https://example.com/posts/3", + "actor": { + "type": "Person", + "name": "oktay_ozel", + "image": { + "type": "Image", + "url": "https://private-user-images.githubusercontent.com/57640531/310137517-cbe7aa9f-3457-4f64-b37b-c3e46d4e448b.png" + } + }, + "object": { + "type": "Note", + "name": "Serendipity: A Fortunate Discovery", + "content": "Serendipity is the occurrence of events by chance in a happy or beneficial way. Example: 'They met by serendipity and became lifelong friends.'", + "tag": [ + { + "type": "Hashtag", + "name": "Vocabulary" + } + ], + "published": "2024-11-05T20:00:00Z", + "replies": { + "type": "Collection", + "totalItems": 5 + }, + "likes": { + "type": "Collection", + "totalItems": 75 + } + } + } + ], + "quizzes": [ + { + "type": "Quiz", + "id": "https://example.com/quizzes/1", + "name": "Basic Vocabulary Quiz", + "summary": "Test your knowledge on basic vocabulary words!", + "attributedTo": { + "type": "Person", + "name": "oktay_ozel" + }, + "audienceLevel": "Beginner", + "likes": { + "type": "Collection", + "totalItems": 5 + } + }, + { + "type": "Quiz", + "id": "https://example.com/quizzes/2", + "name": "Advanced Vocabulary Quiz", + "summary": "Challenge yourself with advanced vocabulary words.", + "attributedTo": { + "type": "Person", + "name": "oktay_ozel" + }, + "audienceLevel": "Advanced", + "likes": { + "type": "Collection", + "totalItems": 9 + } + } + ] +} + return Response(mock_profile, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/backend/data/db_files/relationships.csv b/backend/data/db_files/relationships.csv new file mode 100644 index 00000000..5bc5e1c9 --- /dev/null +++ b/backend/data/db_files/relationships.csv @@ -0,0 +1,105 @@ +word,related_word,relation_type +a,synset-A-noun-6,synonym +a,letter,broader +abandon,synset-abandon-verb-1,synonym +abandon,discard,broader +abandon,chuck,narrower +abandon,forfeit,narrower +abandon,dispense,narrower +abandon,consign,narrower +abandon,synset-wildness-noun-1,synonym +abandon,passion,broader +abandon,synset-abandon-verb-2,synonym +abandon,foreswear,narrower +abandon,synset-abandon-noun-1,synonym +abandon,unrestraint,broader +abandon,synset-abandon-verb-10,synonym +abandon,synset-abandon-verb-1,synonym +abandon,leave,broader +abandon,maroon,narrower +abandon,expose,narrower +abandon,ditch,narrower +abandon,walk,narrower +abandon,synset-vacate-verb-2,synonym +abandon,leave,broader +abandoned,synset-abandoned-adjectivesatellite-2,synonym +abandoned,synset-abandoned-adjectivesatellite-1,synonym +ability,c,broader +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,c,narrower +ability,sh85082114,narrower +ability,sh2005004875,narrower +ability,sh85142795,narrower +ability,sh85041103,narrower +ability,sh2008004738,narrower +ability,sh97008210,narrower +ability,sh85082750,narrower +ability,sh85075527,narrower +ability,sh85076844,narrower +ability,sh85075480,narrower +ability,sh85009146,narrower +ability,sh2008008130,narrower +ability,sh89002679,narrower +ability,sh2010000098,narrower +ability,sh85126514,narrower +ability,sh85046278,narrower +ability,sh85033833,narrower +ability,sh85088948,narrower +ability,sh85087575,narrower +ability,sh00002431,narrower +ability,sh85067157,narrower +ability,synset-ability-noun-3,synonym +ability,cognition,broader +ability,know-how,narrower +ability,hand,narrower +ability,skill,narrower +ability,intelligence,narrower +ability,creativity,narrower +ability,aptitude,narrower +ability,bilingualism,narrower +ability,leadership,narrower +ability,faculty,narrower +ability,superior,narrower +ability,capacity,narrower +ability,skill,narrower +ability,originality,narrower +ability,synset-ability-noun-1,synonym +ability,quality,broader +ability,totipotency,narrower +ability,Midas,narrower +ability,immunocompetence,narrower +ability,physical,narrower +ability,penetration,narrower +ability,magical,narrower +ability,interoperability,narrower +ability,form,narrower +ability,adaptability,narrower +ability,competence,narrower +ability,sensitivity,narrower +ability,capability,narrower +ability,contractility,narrower +able,synset-able-adjectivesatellite-5,synonym +able,synset-able-adjectivesatellite-1,synonym +able,synset-able-adjectivesatellite-3,synonym +able,synset-able-adjective-1,synonym +abnormal,synset-abnormal-adjective-2,synonym +abnormal,synset-abnormal-adjectivesatellite-3,synonym +abnormal,synset-abnormal-adjective-2,synonym +abnormally,synset-abnormally-adverb-1,synonym +aboard,synset-aboard-adverb-1,synonym +aboard,synset-aboard-adverb-4,synonym +aboard,synset-aboard-adverb-1,synonym +aboard,synset-aboard-adverb-1,synonym +abolish,synset-abolish-verb-3,synonym +abolish,cashier,narrower +abolish,abrogate,narrower diff --git a/backend/data/db_files/translations.csv b/backend/data/db_files/translations.csv new file mode 100644 index 00000000..5ea0ada5 --- /dev/null +++ b/backend/data/db_files/translations.csv @@ -0,0 +1,8 @@ +word,turkish_translation +a,a +a,bir +abandon,terk etmek +abandon,bırakmak +abandon,kovmak +abandoned,terk edilmiş +abnormal,anormal diff --git a/backend/data/db_files/words.csv b/backend/data/db_files/words.csv new file mode 100644 index 00000000..4418534e --- /dev/null +++ b/backend/data/db_files/words.csv @@ -0,0 +1,26 @@ +word,explanation,sentence,level,part_of_speech +a,the 1st letter of the Roman alphabet,,A1,determiner +abandon,"forsake, leave behind;",We abandoned the old car in the empty parking lot,B1,verb +abandon,a feeling of extreme emotional intensity;,the wildness of his anger,B1,verb +abandon,give up with the intent of never claiming again;,"Abandon your life to God""; ""She gave up her children to her ex-husband when she moved to Tahiti""; ""We gave the drowning victim up for dead",B1,verb +abandon,the trait of lacking restraint or control; reckless freedom from inhibition or worry;,she danced with abandon,B1,verb +abandon,stop maintaining or insisting on; of ideas or claims;,"He abandoned the thought of asking for her hand in marriage""; ""Both sides have to give up some claims in these negotiations",B1,verb +abandon,leave someone who needs or counts on you; leave in the lurch;,The mother deserted her children,B1,verb +abandon,leave behind empty; move out of;,You must vacate your office by tonight,B1,verb +abandoned,free from constraint;,"an abandoned sadness born of grief""- Liam O'Flaherty",B2,adjective +abandoned,forsaken by owner or inhabitants ;,weed-grown yard of an abandoned farmhouse,B2,adjective +ability,possession of the qualities (especially mental qualities) required to do something or get something done;,danger heightened his powers of discrimination,A2,noun +ability,the quality of being able to perform; a quality that permits or facilitates achievement or accomplishment,,A2,noun +able,have the skills and qualifications to do things well;,"able teachers""; ""a capable administrator""; ""children as young as 14 can be extremely capable and dependable",B1,adjective +able,having a strong healthy body;,"an able seaman""; ""every able-bodied young man served in the army",B1,adjective +able,having inherent physical or mental ability or capacity;,"able to learn""; ""human beings are able to walk on two feet""; ""Superman is able to leap tall buildings",B1,adjective +able,(usually followed by `to') having the necessary means or skill or know-how or authority to do something;,"able to swim""; ""she was able to program her computer""; ""we were at last able to buy a car""; ""able to get a grant for the project",B1,adjective +abnormal,not normal; not typical or usual or regular or conforming to a norm;,"abnormal powers of concentration""; ""abnormal amounts of rain""; ""abnormal circumstances""; ""an abnormal interest in food",B1,adjective +abnormal,much greater than the normal;,"abnormal profits""; ""abnormal ambition",B1,adjective +abnormal,departing from the normal in e.g. intelligence and development;,"they were heartbroken when they learned their child was abnormal""; ""an abnormal personality",B1,adjective +abnormally,in an abnormal manner;,"they were behaving abnormally""; ""his blood pressure was abnormally low",B2,adverb +aboard,side by side;,anchored close aboard another ship,B1,adverb +aboard,part of a group;,Bill's been aboard for three years now,B1,adverb +aboard,on first or second or third base;,Their second homer with Bob Allison aboard,B1,adverb +aboard,"on a ship, train, plane or other vehicle",,B1,adverb +abolish,do away with;,Slavery was abolished in the mid-19th century in America and in Russia,B2,verb diff --git a/docker-compose.yml b/docker-compose.yml index 5b02a63f..8f769b52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,9 +4,9 @@ services: container_name: postgres_db restart: always environment: - POSTGRES_DB: ${DB_NAME} - POSTGRES_USER: ${DB_USER} - POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: db + POSTGRES_USER: halil + POSTGRES_PASSWORD: 123 volumes: - ./dbdata:/var/lib/postgresql/data ports: diff --git a/frontend/src/components/common/navbar.tsx b/frontend/src/components/common/navbar.tsx index 2bd696cc..d3ced483 100644 --- a/frontend/src/components/common/navbar.tsx +++ b/frontend/src/components/common/navbar.tsx @@ -1,5 +1,16 @@ -import { Popover, PopoverTrigger, PopoverContent, Avatar, Card, Input, Button, Divider } from "@nextui-org/react"; +import { + Popover, + PopoverTrigger, + PopoverContent, + Avatar, + Card, + Input, + Button, + Divider, + Badge, +} from "@nextui-org/react"; import { useNavigate, useLocation } from "react-router-dom"; +import { IconBell } from "@tabler/icons-react"; export default function Navbar() { const navigate = useNavigate(); @@ -60,7 +71,18 @@ export default function Navbar() { -
+
+ + +
); } - diff --git a/frontend/src/components/notification/notification-card.tsx b/frontend/src/components/notification/notification-card.tsx new file mode 100644 index 00000000..164258e7 --- /dev/null +++ b/frontend/src/components/notification/notification-card.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { Card, CardBody, Avatar } from "@nextui-org/react"; + +type Props = { + content: string; + timePassed: string; +}; + +export default function NotificationCard({ content, timePassed }: Props) { + return ( + + +
+ +

{content}

+
+

{timePassed}

+
+
+ ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f45807c6..ad6d0415 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -13,6 +13,7 @@ import Browse from "./pages/browse.tsx"; import ComposePost from "./pages/compose-post.tsx"; import QuizEnd from "./pages/quiz-end.tsx"; import QuizDetails from "./pages/quiz-details.tsx"; +import Notifications from "./pages/notifications.tsx"; import "./index.css"; @@ -57,6 +58,10 @@ const { router } = typesafeBrowserRouter([ path: "/browse", Component: Browse, }, + { + path: "/notifications", + Component: Notifications, + }, ]); ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/pages/notifications.tsx b/frontend/src/pages/notifications.tsx new file mode 100644 index 00000000..0bcbd1a5 --- /dev/null +++ b/frontend/src/pages/notifications.tsx @@ -0,0 +1,37 @@ +import Navbar from "../components/common/navbar.tsx"; +import NotificationCard from "../components/notification/notification-card.tsx"; + +export default function Notifications() { + const notifications = [ + { + id: "1233", + content: "alitariksahin liked your post.", + timePassed: "1h ago", + }, + { + id: "1245", + content: "elifndeniz solved your quiz.", + timePassed: "2h ago", + }, + { + id: "1257", + content: "oktayozel started following you.", + timePassed: "1d ago", + }, + ]; + + return ( +
+ +
+ {notifications.map((notification) => ( + + ))} +
+
+ ); +} diff --git a/lab_report.md b/lab_report.md new file mode 100644 index 00000000..e834cde4 --- /dev/null +++ b/lab_report.md @@ -0,0 +1,45 @@ +## 📢 Meeting Information + +| 📅 Date | 🕐 Time | ⏲️ Duration | 📍 Context | 👤 Host | +|---|---|---|---|---| +|5 November 2024 | 15:00 | 2 hours | BM B3 | Oktay Özel | + +## 👤 Attendees +| ❔ | 👤 Member | +|---|----------------------| +| 🟢 | Anıl Köse | +| 🟢 | Aras Taşçı | +| 🟢 | Ahmet Oğuz Engin | +| 🟢 | Oktay Özel | +| 🟢 | Yağız Güldal | +| 🟢 | Ali Tarık Şahin | +| 🟢 | Elif Nur Deniz | +| 🟢 | Yunus Emre Özdemir | +| 🟢 | Halil Özkan | +| 🟢 | Kaan Yolcu | + +## 📅 Agenda: +* Reviewıng the current project situation. +* Catching up with the deadlines for this weekend. +* Creating a new branch for the lab and doing co-authored commits and a pull request to main. +* Research on web standards and on activity streams 2.0. +* Documenting the research on wiki page. +* Pushing those researches to our branch. +* Quiz Finish, and Results pages are discussed. List of the tags are discussed. +* Http request issue of the mobile team has been +* Mobile design options has been discussed. + +## 📌 Outcomes: +* We have decided to continue with activity streams 2.0 +* Frontend team will be documenting the outcomes. +* Forum page will be totally connected to the frontend by this weekend and will be up. +* Mobile team will solve the issues related to the network problem. +* Halil will take responsibility of the Lab Report. + +## 🚀 Action Items: +| 🚀 Action | 👤 Assigned to | ❗️ Deadline | Issue | +|--------------------|------------------------|------------------------|------------------------| +| Lab Report | Halil Özkan | 5 November 2024 | No Issue| + +| Create Post Activity Streams Implementation | Kaan Yolcu | 5 November 2024 | No Issue| +| Like Unlike Post Activity Streams Implementation | Oktay Özel | 5 November 2024 | No Issue| diff --git a/mobile/bulingo/app/(tabs)/_layout.tsx b/mobile/bulingo/app/(tabs)/_layout.tsx index 790ab659..fd5f4c3f 100644 --- a/mobile/bulingo/app/(tabs)/_layout.tsx +++ b/mobile/bulingo/app/(tabs)/_layout.tsx @@ -36,6 +36,13 @@ export default function TabLayout() { tabBarIcon: ({ color }) => , }} /> + , // FontAwesome bell icon for notifications + }} + /> , }} /> + + ); diff --git a/mobile/bulingo/app/(tabs)/notifications.tsx b/mobile/bulingo/app/(tabs)/notifications.tsx new file mode 100644 index 00000000..e9a72af3 --- /dev/null +++ b/mobile/bulingo/app/(tabs)/notifications.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { View, Text, FlatList, Image, StyleSheet } from 'react-native'; + +const notifications = [ + { id: '1', text: 'Oguz bookmarked your quiz.' }, + { id: '2', text: 'Halil followed you.' }, + { id: '3', text: 'Anil followed you.' }, + { id: '4', text: 'Aras bookmarked your quiz.' }, + { id: '5', text: 'Oguz followed you.' }, + { id: '6', text: 'Ozan liked your comment.' }, + { id: '7', text: 'Sait bookmarked your quiz.' }, + { id: '8', text: 'Kasap liked your post.' }, + { id: '9', text: 'Ozan followed you.' }, + { id: '10', text: 'Kasap followed you.' }, + { id: '11', text: 'Sait liked your quiz.' }, + { id: '12', text: 'Ozan bookmarked your post.' }, + { id: '13', text: 'Kasap liked your recent quiz.' }, + { id: '14', text: 'Sait followed you.' }, + { id: '16', text: 'Halil bookmarked your comment.' }, + { id: '17', text: 'Anil liked your post.' }, + ]; + +const NotificationItem = ( {text} : any ) => ( + + + + + {text} + +); + +const Notifications = () => { + return ( + + } + keyExtractor={(item) => item.id} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + backgroundColor: '#f0f0f0', + }, + notificationContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'white', + padding: 16, + marginVertical: 4, + borderRadius: 8, + elevation: 4, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + }, + profileInfoTopPictureContainer: { + marginRight: 12, + }, + profileInfoTopPicture: { + width: 40, + height: 40, + borderRadius: 20, + }, + notificationText: { + fontSize: 16, + color: '#1a73e8', // Blue for title text + }, +}); + +export default Notifications; diff --git a/mobile/bulingo/app/pressableText.tsx b/mobile/bulingo/app/pressableText.tsx new file mode 100644 index 00000000..f05e35e4 --- /dev/null +++ b/mobile/bulingo/app/pressableText.tsx @@ -0,0 +1,98 @@ +import React, {useState} from 'react'; +import { View, Pressable, Text, StyleSheet, Modal} from 'react-native'; + + +type PressableTextProps = { + text: string, + getWordInfo: (word: string) => string; +}; + +export default function PressableText(props: PressableTextProps){ + const [selectedWord, setSelectedWord] = useState(null); + const [modalVisible, setModalVisible] = useState(false);// Function to handle press and show modal + const handleLongPress = (word: string) => { + setSelectedWord(word); + setModalVisible(true); + }; + + // Function to close modal + const closeModal = () => { + setModalVisible(false); + setSelectedWord(null); + }; + + return ( + + {/* Render each word as a Pressable */} + {props.text.split(' ').map((word, index) => ( + handleLongPress(word)} + style={styles.wordPressable} + > + {word} + + ))} + + {/* Modal for additional information */} + {selectedWord && ( + + + + + {props.getWordInfo(selectedWord)} + + + Close + + + + + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + flexWrap: 'wrap', + }, + wordPressable: { + margin: 2, + }, + wordText: { + fontSize: 16, + color: 'blue', + }, + modalBackground: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + modalContent: { + backgroundColor: 'white', + padding: 20, + borderRadius: 10, + alignItems: 'center', + }, + modalText: { + fontSize: 18, + marginBottom: 10, + }, + closeButton: { + marginTop: 10, + padding: 10, + backgroundColor: 'grey', + borderRadius: 5, + }, + closeButtonText: { + color: 'white', + }, +}); \ No newline at end of file