diff --git a/app/(tabs)/messages.tsx b/app/(tabs)/messages.tsx index e69de29..c1e21de 100644 --- a/app/(tabs)/messages.tsx +++ b/app/(tabs)/messages.tsx @@ -0,0 +1,78 @@ +import React, { useCallback, useReducer } from 'react'; +import { ThemedText, ThemedView } from '@/components/theme/Theme'; +import { Alert, Linking, Platform, StyleSheet } from 'react-native'; +import { MaterialIcons } from '@expo/vector-icons'; +import { + GiftedChat, + IMessage, + Send, + SendProps, + SystemMessage, +} from 'react-native-gifted-chat'; +import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; +import AccessoryBar from '@/components/chat/AccessoryBar'; +import CustomActions from '@/components/chat/CustomActions'; +import CustomView from '@/components/chat/CustomView'; +import NavBar from '@/components/chat/NavBar'; +import earlierMessages from '@/components/chat/data/earlierMessages'; +import messages from '@/components/chat/data/messages'; +import * as Clipboard from 'expo-clipboard'; +import { + GCUser, + GCState, + GCStateAction, + ActionKind, + GCMessage +} from '@/constants/Types'; + +const tempUser: GCUser = { + _id: 1, + name: 'Developer', +}; + +const reducer = (state: GCState, action: GCStateAction) => { + switch (action.type) { + case ActionKind.SEND_MESSAGE: { + return { + ...state, + step: state.step + 1, + messages: action.payload, + }; + } + case ActionKind.LOAD_EARLIER_MESSAGES: { + return { + ...state, + loadEarlier: true, + isLoadingEarlier: false, + messages: action.payload, + }; + } + case ActionKind.LOAD_EARLIER_START: { + return { + ...state, + isLoadingEarlier: true, + } + } + case ActionKind.SET_IS_TYPING: { + return { + ...state, + isTyping: action.payload, + } + } + } +}; + +const MessagesScreen = () => { + const [state, dispatch] = useReducer(reducer, { + messages: messages, + step: 0, + loadEarlier: true, + isLoadingEarlier: false, + isTyping: false, + }) + + const onSend = useCallback((messages: GCMessage[]) => { + const sentMessages = [{ ...messages[0], sent: true, received: true }] + +}; +export default MessagesScreen; diff --git a/components/chat/CustomView.tsx b/components/chat/CustomView.tsx index e69de29..0618923 100644 --- a/components/chat/CustomView.tsx +++ b/components/chat/CustomView.tsx @@ -0,0 +1,83 @@ +import React, { useCallback } from 'react'; +import * as Linking from 'expo-linking'; +import { + Platform, + StyleSheet, + StyleProp, + ViewStyle +} from 'react-native'; +import { ThemedText, ThemedView } from '@/components/theme/Theme'; +import MapView from 'react-native-maps'; +import { TouchableOpacity } from 'react-native-gesture-handler'; + +type Props = { + currentMessage: any; + containerStyle?: StyleProp; + mapViewStyle?: StyleProp; +}; + +const CustomView = ({ currentMessage, containerStyle, mapViewStyle }: Props) => { + const openMapAsync = useCallback(async () => { + if (Platform.OS === 'web') { + alert('Opening the map is not supported.'); + return; + } + const {location = {} } = currentMessage; + const url = Platform.select({ + ios: `http://maps.apple.com/?ll=${location.latitude},${location.longitude}`, + default: `http://maps.google.com/?q=${location.latitude},${location.longitude}`, + }); + try { + const supported = await Linking.canOpenURL(url); + if (supported) return Linking.openURL(url); + else alert('Opening the map is not supported.'); + } catch (error) { + console.log('Could not open link!', error.message); + alert(error.message); + } + }, [currentMessage]); + + if (currentMessage.location) { + return ( + + {Platform.OS !== 'web' + ? ( + + ) + : ( + + + Map not in web yet, sorry! + + + )} + + ); + } + return null; +}; +export default CustomView; + +const styles = StyleSheet.create({ + container: { + }, + mapView: { + width: 150, + height: 100, + borderRadius: 13, + margin: 3, + }, +}); diff --git a/components/chat/NavBar.tsx b/components/chat/NavBar.tsx index e69de29..7d9588f 100644 --- a/components/chat/NavBar.tsx +++ b/components/chat/NavBar.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { ThemedText, ThemedView } from '../theme/Theme'; +import { Platform, StyleSheet } from 'react-native'; + +const NavBar = () => { + if (Platform.OS === 'web') return null; + return ( + + 💬 Chat{'\n'} + + ); +}; +export default NavBar; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + paddingTop: 10, + }, +}); diff --git a/components/chat/data/earlierMessages.js b/components/chat/data/earlierMessages.js index e69de29..8fd3eae 100644 --- a/components/chat/data/earlierMessages.js +++ b/components/chat/data/earlierMessages.js @@ -0,0 +1,129 @@ +export default () => [ + { + _id: Math.round(Math.random() * 1000000), + text: + 'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: + 'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: + 'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: + 'It uses the same design as React, letting you compose a rich mobile UI from declarative components https://facebook.github.io/react-native/', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'React Native lets you build mobile apps using only JavaScript', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: Math.round(Math.random() * 1000000), + text: 'This is a system message.', + createdAt: new Date(Date.UTC(2016, 7, 30, 17, 20, 0)), + system: true, + }, +] diff --git a/components/chat/data/messages.js b/components/chat/data/messages.js index e69de29..8ab4293 100644 --- a/components/chat/data/messages.js +++ b/components/chat/data/messages.js @@ -0,0 +1,168 @@ +export default [ + { + _id: 9, + text: '#awesome 3', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 8, + text: '#awesome 2', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 7, + text: '#awesome', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 6, + text: 'Paris', + createdAt: new Date(), + user: { + _id: 2, + name: 'React Native', + }, + image: + 'https://www.xtrafondos.com/wallpapers/torre-eiffel-en-paris-415.jpg', + sent: true, + received: true, + }, + { + _id: 5, + text: 'Send me a picture!', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 4, + text: '', + createdAt: new Date(), + user: { + _id: 2, + name: 'React Native', + }, + sent: true, + received: true, + location: { + latitude: 48.864601, + longitude: 2.398704, + }, + }, + { + _id: 3, + text: 'Where are you?', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 2, + text: 'Yes, and I use #GiftedChat!', + createdAt: new Date(), + user: { + _id: 2, + name: 'React Native', + }, + sent: true, + received: true, + }, + { + _id: 1, + text: 'Are you building a chat app?', + createdAt: new Date(), + user: { + _id: 1, + name: 'Developer', + }, + }, + { + _id: 10, + text: 'This is a quick reply. Do you love Gifted Chat? (radio) KEEP IT', + createdAt: new Date(), + quickReplies: { + type: 'radio', // or 'checkbox', + keepIt: true, + values: [ + { + title: '😋 Yes', + value: 'yes', + }, + { + title: + '📷 Yes, let me show you with a picture! Again let me show you with a picture!', + value: 'yes_picture', + }, + { + title: '😞 Nope. What?', + value: 'no', + }, + ], + }, + user: { + _id: 2, + name: 'React Native', + }, + }, + { + _id: 20, + text: 'This is a quick reply. Do you love Gifted Chat? (checkbox)', + createdAt: new Date(), + quickReplies: { + type: 'checkbox', // or 'checkbox', + values: [ + { + title: 'Yes', + value: 'yes', + }, + { + title: 'Yes, let me show you with a picture!', + value: 'yes_picture', + }, + { + title: 'Nope. What?', + value: 'no', + }, + ], + }, + user: { + _id: 2, + name: 'React Native', + }, + }, + { + _id: 30, + createdAt: new Date(), + video: 'https://media.giphy.com/media/3o6ZthZjk09Xx4ktZ6/giphy.mp4', + user: { + _id: 2, + name: 'React Native', + }, + }, + { + _id: 31, + createdAt: new Date(), + audio: + 'https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3', + user: { + _id: 2, + name: 'React Native', + }, + }, +] diff --git a/constants/Types.ts b/constants/Types.ts index 25db7ca..817e532 100644 --- a/constants/Types.ts +++ b/constants/Types.ts @@ -123,9 +123,28 @@ export type GCMessage = { pending?: boolean; quickReplies?: GCQuickReplies; }; +export type GCState = { + messages: any[]; + step: number; + loadEarlier?: boolean; + isLoadingEarlier?: boolean; + isTyping: boolean; +}; +export enum ActionKind { + SEND_MESSAGE = 'SEND_MESSAGE', + LOAD_EARLIER_MESSAGES = 'LOAD_EARLIER_MESSAGES', + LOAD_EARLIER_START = 'LOAD_EARLIER_START', + SET_IS_TYPING = 'SET_IS_TYPING', + // LOAD_EARLIER_END = 'LOAD_EARLIER_END', +}; +export type GCStateAction = { + type: ActionKind; + payload?: any; +}; export type NotificationMessage = { sound?: string; title: string; body: string; data?: any; }; + diff --git a/package.json b/package.json index eb398d0..395f324 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@react-navigation/native": "^6.0.2", "expo": "~51.0.28", "expo-apple-authentication": "~6.4.2", + "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.2", "expo-device": "~6.0.2", "expo-font": "~12.0.9", @@ -38,6 +39,7 @@ "react-native-gesture-handler": "~2.16.1", "react-native-get-random-values": "~1.11.0", "react-native-gifted-chat": "^2.6.4", + "react-native-maps": "1.14.0", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fa8988..5750b46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: expo-apple-authentication: specifier: ~6.4.2 version: 6.4.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) + expo-clipboard: + specifier: ~6.0.3 + version: 6.0.3(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) expo-constants: specifier: ~16.0.2 version: 16.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))) @@ -80,6 +83,9 @@ importers: react-native-gifted-chat: specifier: ^2.6.4 version: 2.6.4(react-native-get-random-values@1.11.0(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0)))(react-native-reanimated@3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-native-maps: + specifier: 1.14.0 + version: 1.14.0(react-native-web@0.19.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) react-native-reanimated: specifier: ~3.10.1 version: 3.10.1(@babel/core@7.25.8)(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) @@ -1373,6 +1379,9 @@ packages: '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/geojson@7946.0.14': + resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -2306,6 +2315,11 @@ packages: peerDependencies: expo: '*' + expo-clipboard@6.0.3: + resolution: {integrity: sha512-RIKDsuHkYfaspifbFpVC8sBVFKR05L7Pj7mU2/XkbrW9m01OBNvdpGraXEMsTFCx97xMGsZpEw9pPquL4j4xVg==} + peerDependencies: + expo: '*' + expo-constants@16.0.2: resolution: {integrity: sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ==} peerDependencies: @@ -3971,6 +3985,17 @@ packages: react: '>=16.8.0' react-native: '>=0.61.0' + react-native-maps@1.14.0: + resolution: {integrity: sha512-ai7h4UdRLGPFCguz1fI8n4sKLEh35nZXHAH4nSWyAeHGrN8K9GjICu9Xd4Q5Ok4h+WwrM6Xz5pGbF3Qm1tO6iQ==} + engines: {node: '>=18'} + peerDependencies: + react: '>= 17.0.1' + react-native: '>= 0.64.3' + react-native-web: '>= 0.11' + peerDependenciesMeta: + react-native-web: + optional: true + react-native-parsed-text@0.0.22: resolution: {integrity: sha512-hfD83RDXZf9Fvth3DowR7j65fMnlqM9PpxZBGWkzVcUTFtqe6/yPcIoIAgrJbKn6YmtzkivmhWE2MCE4JKBXrQ==} peerDependencies: @@ -7017,6 +7042,8 @@ snapshots: '@types/cookie@0.6.0': {} + '@types/geojson@7946.0.14': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 22.7.5 @@ -8032,6 +8059,10 @@ snapshots: transitivePeerDependencies: - supports-color + expo-clipboard@6.0.3(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): + dependencies: + expo: 51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8)) + expo-constants@16.0.2(expo@51.0.38(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))): dependencies: '@expo/config': 9.0.4 @@ -10082,6 +10113,14 @@ snapshots: react: 18.2.0 react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0) + react-native-maps@1.14.0(react-native-web@0.19.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + dependencies: + '@types/geojson': 7946.0.14 + react: 18.2.0 + react-native: 0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0) + optionalDependencies: + react-native-web: 0.19.12(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-native-parsed-text@0.0.22(react-native@0.74.5(@babel/core@7.25.8)(@babel/preset-env@7.25.8(@babel/core@7.25.8))(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): dependencies: prop-types: 15.8.1