diff --git a/package.json b/package.json
index 278004e..b413f67 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
},
"dependencies": {
"@gorhom/animated-tabbar": "^2.1.1",
+ "@gorhom/bottom-sheet": "^3",
"@react-native-async-storage/async-storage": "^1.15.1",
"@react-native-community/google-signin": "^5.0.0",
"@react-native-community/masked-view": "^0.1.10",
@@ -48,7 +49,9 @@
"react-native-url-polyfill": "^1.3.0",
"react-native-vector-icons": "^8.1.0",
"react-native-web": "^0.15.5",
- "reactotron-mst": "^3.1.3"
+ "reactotron-mst": "^3.1.3",
+ "reanimated-bottom-sheet": "^1.0.0-alpha.22",
+ "use-memo-one": "^1.1.2"
},
"devDependencies": {
"@babel/core": "^7.13.14",
diff --git a/src/app/App.native.tsx b/src/app/App.native.tsx
index ef3492e..6f304af 100644
--- a/src/app/App.native.tsx
+++ b/src/app/App.native.tsx
@@ -1,12 +1,16 @@
import { NavigationContainer } from "@react-navigation/native";
import React, { useContext } from "react";
-import { Platform } from "react-native";
+import { Platform, StatusBar, View } from "react-native";
import "react-native-gesture-handler";
-import { DarkTheme, Provider as PaperProvider } from "react-native-paper";
+import {
+ DarkTheme,
+ Provider as PaperProvider,
+ Title,
+ useTheme,
+} from "react-native-paper";
import SwipeTabNavigation from "./Routes/MainSwipeNavigation";
import SignInNavigation from "./Routes/SignInNavigation";
import AppContextProvider, { AppContext } from "./utils/appContext";
-
// eslint-disable-next-line no-undef
const theme: ReactNativePaper.Theme = {
...DarkTheme,
@@ -20,7 +24,28 @@ const theme: ReactNativePaper.Theme = {
};
const Main = () => {
- const { signupDone } = useContext(AppContext);
+ const { loading, signupDone } = useContext(AppContext);
+ const { colors, dark } = useTheme();
+ if (loading)
+ return (
+ <>
+
+
+ Instaclone
+
+ >
+ );
if (signupDone) return ;
return ;
diff --git a/src/app/Components/Post/PostMenuModal.tsx b/src/app/Components/Post/PostMenuModal.tsx
new file mode 100644
index 0000000..5260aa3
--- /dev/null
+++ b/src/app/Components/Post/PostMenuModal.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { ToastAndroid, useWindowDimensions, View } from "react-native";
+import { List } from "react-native-paper";
+import { useNavigation } from "@react-navigation/native";
+import PostsStore from "../../store/PostsStore";
+
+type ModalProps = {
+ closeModal: () => void;
+ ownPost: boolean;
+ postId: number;
+ username: string;
+ viewProfile: () => void;
+};
+
+export const PostModal: React.FC = ({
+ postId,
+ closeModal,
+ ownPost,
+ viewProfile,
+}) => {
+ const { width } = useWindowDimensions();
+ const navigation = useNavigation();
+ const deletePost = async () => {
+ try {
+ await PostsStore.deletePost(postId);
+ closeModal();
+ navigation.goBack();
+ } catch (err) {
+ console.error("[deletePost]", err);
+ ToastAndroid.show("An error occured", ToastAndroid.LONG);
+ }
+ };
+
+ const openProfile = () => {
+ closeModal();
+ viewProfile();
+ };
+
+ return (
+
+ {ownPost && (
+
+ )}
+
+
+
+ );
+};
diff --git a/src/app/Components/Post/PostShareModal.tsx b/src/app/Components/Post/PostShareModal.tsx
new file mode 100644
index 0000000..2d6ce33
--- /dev/null
+++ b/src/app/Components/Post/PostShareModal.tsx
@@ -0,0 +1,178 @@
+import React, { useContext, useEffect, useState } from "react";
+import {
+ ActivityIndicator,
+ FlatList,
+ StatusBar,
+ TextInput,
+ ToastAndroid,
+ useWindowDimensions,
+ View,
+} from "react-native";
+import { Divider, Text, useTheme } from "react-native-paper";
+import NewChatItem, {
+ NewChatItemType,
+} from "../../Screens/Messages/NewChatItem";
+import { Follower } from "../../store/FollowersStore";
+import MessagesStore, { Message } from "../../store/MessagesStore";
+import { AppContext } from "../../utils/appContext";
+import { newMessageInDb } from "../../utils/supabaseUtils";
+import useChatList, { ChatList } from "../../utils/useChatList";
+import useUser from "../../utils/useUser";
+
+type ModalProps = {
+ closeModal: () => void;
+ postId: number;
+};
+
+const PostShareModal: React.FC = ({ postId, closeModal }) => {
+ const { colors } = useTheme();
+ const { width, height } = useWindowDimensions();
+ const { messageList, loading } = useChatList();
+ const [searchTerm, setSearchTerm] = useState("");
+
+ const { username: currentUsername } = useContext(AppContext);
+ const { following } = useUser(currentUsername);
+
+ const [chatList, setChatList] = useState([]);
+
+ const getUsersList = (chatsList: ChatList[], followingList: Follower[]) => {
+ const followingListData = followingList.map((user) => ({
+ username: user.following,
+ }));
+
+ const chatListData = [...chatsList, ...followingListData];
+ return [
+ ...new Map(
+ chatListData.map((item) => [item.username, item])
+ ).values(),
+ ];
+ };
+
+ const [searchResults, setSearchResults] = useState<
+ NewChatItemType[] | null
+ >(null);
+
+ useEffect(() => {
+ if (searchTerm.length > 0) {
+ setSearchResults(
+ messageList.filter((item) =>
+ item.username.includes(searchTerm.toLowerCase())
+ )
+ );
+ } else {
+ setSearchResults(null);
+ }
+ }, [messageList, searchTerm]);
+
+ useEffect(() => {
+ if (messageList && following)
+ setChatList(getUsersList(messageList, following));
+ }, [messageList, following]);
+
+ useEffect(() => {
+ if (searchTerm.length > 0) {
+ setSearchResults(
+ messageList.filter((item) =>
+ item.username.includes(searchTerm.toLowerCase())
+ )
+ );
+ } else {
+ setSearchResults(null);
+ }
+ }, [messageList, searchTerm]);
+
+ const newMessage = async (username: string) => {
+ const messageToSend = {
+ imageUrl: undefined,
+ message_type: "POST",
+ postId: postId,
+ receiver: username,
+ text: undefined,
+ };
+ const newMessageData = await newMessageInDb(messageToSend);
+ if (newMessageData) {
+ MessagesStore.addMessage(newMessageData);
+ ToastAndroid.show("Post sent", ToastAndroid.LONG);
+ }
+ closeModal();
+ };
+
+ return (
+
+ {loading && (
+
+
+
+ )}
+
+
+ setSearchTerm(text)}
+ style={{
+ flex: 1,
+ marginHorizontal: 16,
+ marginVertical: 16,
+ height: 40,
+ backgroundColor: "#3a3a3a",
+ borderRadius: 6,
+ paddingHorizontal: 16,
+ color: colors.text,
+ }}
+ />
+
+
+ Suggested
+
+ >
+ }
+ ListEmptyComponent={
+
+ Nothing to see here, yet.
+
+ }
+ data={searchResults ? searchResults : chatList}
+ ItemSeparatorComponent={Divider}
+ renderItem={({ item }) => (
+
+ )}
+ keyExtractor={(item) => item.username}
+ bouncesZoom
+ bounces
+ />
+
+ );
+};
+
+export default PostShareModal;
diff --git a/src/app/Components/Post/index.tsx b/src/app/Components/Post/index.tsx
index 11faaaa..2abef01 100644
--- a/src/app/Components/Post/index.tsx
+++ b/src/app/Components/Post/index.tsx
@@ -1,101 +1,32 @@
-import React, { useContext, useEffect, useState } from "react";
-import {
- Image,
- Text,
- ToastAndroid,
- useWindowDimensions,
- View,
-} from "react-native";
+import React, { useState } from "react";
+import { Image, Text, useWindowDimensions } from "react-native";
import {
Caption,
Card,
IconButton,
- List,
Paragraph,
Title,
useTheme,
} from "react-native-paper";
-import { format, formatDistanceToNow } from "date-fns";
import Icon from "react-native-vector-icons/Ionicons";
-import { AppContext } from "../../utils/appContext";
import { useNavigation } from "@react-navigation/native";
-import Modal from "react-native-modal";
import { UserAvatar } from "../UserAvatar";
-import PostsStore, { Post as PostType } from "../../store/PostsStore";
+import { Post as PostType } from "../../store/PostsStore";
import usePost from "../../utils/usePost";
-import supabaseClient from "../../utils/supabaseClient";
-import { definitions } from "../../types/supabase";
import { getTimeDistance } from "../../utils/utils";
-type ModalProps = {
- closeModal: () => void;
- ownPost: boolean;
- postId: number;
- username: string;
- viewProfile: () => void;
-};
-const PostModal: React.FC = ({
- postId,
- closeModal,
- ownPost,
- viewProfile,
-}) => {
- const { width } = useWindowDimensions();
- const navigation = useNavigation();
- const deletePost = async () => {
- try {
- await PostsStore.deletePost(postId);
- closeModal();
- navigation.goBack();
- } catch (err) {
- console.error("[deletePost]", err);
- ToastAndroid.show("An error occured", ToastAndroid.LONG);
- }
- };
-
- const openProfile = () => {
- closeModal();
- viewProfile();
- };
- return (
-
- {ownPost && (
-
- )}
-
-
-
- );
-};
-
-type User = {
- username: string;
- profilePic?: string | null;
-};
+import { User } from "../../store/UsersStore";
+import useUser from "../../utils/useUser";
type Props = Pick & {
- user: User;
+ user: Pick;
+ openModal: (
+ modalType: "MENU" | "SHARE",
+ username: string,
+ postId: number
+ ) => void;
+
+ closeModal: (modalType: "MENU" | "SHARE") => void;
};
const Post: React.FC = ({
@@ -104,6 +35,7 @@ const Post: React.FC = ({
postId,
postedAt,
user: { username, profilePic },
+ openModal,
}) => {
const navigation = useNavigation();
@@ -113,43 +45,9 @@ const Post: React.FC = ({
const [expandedCaption, setExpandedCaption] = useState(false);
const toggleExpandCaption = () => setExpandedCaption(!expandedCaption);
- const { username: currentUsername } = useContext(AppContext);
-
const { liked, likes, toggleLike } = usePost(postId);
- const [userProfilePic, setUserProfilePic] = useState(null);
- useEffect(() => {
- (async () => {
- if (!username) return;
- if (profilePic) {
- setUserProfilePic(profilePic);
- return;
- } else {
- const userRes = await supabaseClient
- .from("users")
- .select("*")
- .eq("username", username);
-
- if (userRes.error || userRes.data.length === 0) {
- console.error(
- "[fetchUserProfilePic_Response]",
- userRes.error
- );
- setUserProfilePic(null);
- return;
- } else {
- if (userRes.data[0].profilePic)
- setUserProfilePic(userRes.data[0].profilePic);
- }
- }
- })();
- }, [username, profilePic]);
-
- const [showModal, setShowModal] = useState(false);
-
- const openModal = () => setShowModal(true);
-
- const closeModal = () => setShowModal(false);
+ const { user } = useUser(username);
const goBack = () => navigation.goBack();
@@ -160,29 +58,28 @@ const Post: React.FC = ({
goBack,
});
+ const openComments = () =>
+ navigation.navigate("Comments", {
+ post: {
+ caption,
+ postedAt,
+ postId,
+ },
+ user: {
+ username,
+ profilePic: profilePic || user?.profilePic,
+ },
+ });
+
+ const openMenuModal = () => openModal("MENU", username, postId);
+ const openShareModal = () => openModal("SHARE", username, postId);
+
return (
<>
-
-
-
@@ -190,7 +87,7 @@ const Post: React.FC = ({
title={username}
left={() => (
)}
@@ -198,7 +95,7 @@ const Post: React.FC = ({
)}
titleStyle={{
@@ -245,23 +142,23 @@ const Post: React.FC = ({
paddingRight: 0,
}}
color={colors.text}
- onPress={() => {
- navigation.navigate("Comments", {
- post: {
- caption,
- postedAt,
- postId,
- },
- user: {
- username,
- profilePic,
- },
- });
- }}
+ onPress={openComments}
backgroundColor="transparent"
name="chatbubble-outline"
size={22}
/>
+
{likes.length > 0 && (
diff --git a/src/app/Components/PostBottomSheetWrapper.tsx b/src/app/Components/PostBottomSheetWrapper.tsx
new file mode 100644
index 0000000..31efde8
--- /dev/null
+++ b/src/app/Components/PostBottomSheetWrapper.tsx
@@ -0,0 +1,163 @@
+import { useNavigation } from "@react-navigation/core";
+import React, { useContext, useEffect, useRef } from "react";
+import {
+ StyleSheet,
+ TouchableWithoutFeedback,
+ useWindowDimensions,
+ View,
+} from "react-native";
+import Animated from "react-native-reanimated";
+import BottomSheet from "reanimated-bottom-sheet";
+import { useMemoOne } from "use-memo-one";
+import { AppContext } from "../utils/appContext";
+import { PostModal } from "./Post/PostMenuModal";
+import PostShareModal from "./Post/PostShareModal";
+
+type Props = {
+ openedModalData: {
+ modalType: "MENU" | "SHARE";
+ username: string;
+ postId: number;
+ } | null;
+ onClose: () => void;
+};
+const PostBottomSheetWrapper: React.FC = ({
+ openedModalData,
+ onClose,
+ children,
+}) => {
+ const navigation = useNavigation();
+ const menuModalref = useRef(null);
+ const shareModalRef = useRef(null);
+
+ const { height } = useWindowDimensions();
+ const { username: currentUsername } = useContext(AppContext);
+
+ useEffect(() => {
+ if (openedModalData?.modalType && openedModalData.postId) {
+ if (openedModalData.modalType === "MENU") {
+ menuModalref.current?.snapTo(0);
+ } else {
+ shareModalRef.current?.snapTo(0);
+ }
+ }
+ }, [openedModalData]);
+
+ const fall = useMemoOne(() => new Animated.Value(1), []);
+
+ const renderHeader = () => (
+
+
+
+
+
+ );
+
+ const renderMenuModal = () =>
+ openedModalData && (
+ closeModal("MENU")}
+ />
+ );
+
+ const renderShareModal = () =>
+ openedModalData && (
+ closeModal("SHARE")}
+ />
+ );
+
+ const goBack = () => navigation.goBack();
+
+ const viewProfile = () =>
+ openedModalData &&
+ navigation.navigate("Profile", {
+ username: openedModalData?.username,
+ isCurrentUser: false,
+ goBack,
+ });
+
+ const closeModal = (modalType: "MENU" | "SHARE") => {
+ if (modalType === "MENU") {
+ menuModalref.current?.snapTo(2);
+ } else {
+ shareModalRef.current?.snapTo(2);
+ }
+ };
+
+ return (
+ <>
+
+
+
+ {children}
+
+ >
+ );
+};
+
+const styles = StyleSheet.create({
+ panelHeader: {
+ width: "100%",
+ height: 24,
+ alignItems: "center",
+ borderTopLeftRadius: 15,
+ borderTopRightRadius: 15,
+ paddingHorizontal: 16,
+ },
+ panelHandle: {
+ width: 40,
+ height: 4,
+ backgroundColor: "#646464",
+ borderRadius: 4,
+ marginVertical: 16,
+ },
+ // Shadow
+ shadowContainer: {
+ ...StyleSheet.absoluteFillObject,
+ backgroundColor: "#000",
+ },
+});
+
+export default PostBottomSheetWrapper;
diff --git a/src/app/Components/PostContainer/index.tsx b/src/app/Components/PostContainer/index.tsx
deleted file mode 100644
index 2091c38..0000000
--- a/src/app/Components/PostContainer/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from "react";
-import { definitions } from "../../types/supabase";
-import Post from "../Post";
-
-export const PostContainer: React.FC<{
- item: definitions["posts"];
-}> = ({ item }) => {
- return (
-
- );
-};
diff --git a/src/app/Routes/MessageStack.tsx b/src/app/Routes/MessageStack.tsx
index 890e6b9..d346494 100644
--- a/src/app/Routes/MessageStack.tsx
+++ b/src/app/Routes/MessageStack.tsx
@@ -7,11 +7,12 @@ import {
} from "../types/navigation";
import Messages from "../Screens/Messages/Messages";
-import MessagesList from "../Screens/Messages";
+import ChatList from "../Screens/Messages/ChatList";
import ProfilePageStack from "./ProfileStack";
import { MaterialTopTabNavigationProp } from "@react-navigation/material-top-tabs";
import PostDetail from "../Screens/Post";
import Comments from "../Screens/Comments";
+import NewChat from "../Screens/Messages/NewChat";
const Stack = createStackNavigator();
@@ -24,13 +25,14 @@ type Props = {
};
const MessageStack: React.FC = ({ navigation }) => (
-
+
+
diff --git a/src/app/Screens/Explore/index.tsx b/src/app/Screens/Explore/index.tsx
index ad9e78c..6b10b55 100644
--- a/src/app/Screens/Explore/index.tsx
+++ b/src/app/Screens/Explore/index.tsx
@@ -225,7 +225,7 @@ const Explore: React.FC = ({ navigation }) => {
post,
user: {
username: post.user,
- profilePic: null,
+ profilePic: undefined,
},
});
}}
diff --git a/src/app/Screens/Home/index.tsx b/src/app/Screens/Home/index.tsx
index 4a0dcc8..731375b 100644
--- a/src/app/Screens/Home/index.tsx
+++ b/src/app/Screens/Home/index.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useState } from "react";
import {
RefreshControl,
FlatList,
@@ -13,12 +13,13 @@ import {
Text,
useTheme,
} from "react-native-paper";
-import { PostContainer } from "../../Components/PostContainer";
import { observer } from "mobx-react-lite";
import useFeed from "../../utils/useFeed";
import { HomeStackNavigationParams } from "../../types/navigation";
import { RouteProp } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
+import Post from "../../Components/Post";
+import PostBottomSheetWrapper from "../../Components/PostBottomSheetWrapper";
type Props = {
route: RouteProp;
@@ -35,6 +36,29 @@ const Home: React.FC = observer(({ route }) => {
const openMessages = () =>
route.params.rootNavigation?.navigate("Messages");
+ const [openedModalData, setOpenedModalData] = useState<{
+ modalType: "MENU" | "SHARE";
+ username: string;
+ postId: number;
+ } | null>(null);
+
+ const closeModal = () => {
+ setOpenedModalData(null);
+ };
+ const onClose = () => setOpenedModalData(null);
+
+ const openModal = (
+ modalType: "MENU" | "SHARE",
+ username: string,
+ postId: number
+ ) => {
+ setOpenedModalData({
+ modalType,
+ postId: postId,
+ username: username,
+ });
+ };
+
return (
= observer(({ route }) => {
barStyle={dark ? "light-content" : "dark-content"}
animated
/>
+
@@ -70,54 +97,63 @@ const Home: React.FC = observer(({ route }) => {
)}
- {!loading && feedPosts && (
-
- Nothing to see here, yet.
-
- }
- data={feedPosts
- .slice()
- .sort(
- (a, b) =>
- new Date(b.postedAt).getTime() -
- new Date(a.postedAt).getTime()
+
+ {!loading && feedPosts && (
+
+ Nothing to see here, yet.
+
+ }
+ data={feedPosts
+ .slice()
+ .sort(
+ (a, b) =>
+ new Date(b.postedAt).getTime() -
+ new Date(a.postedAt).getTime()
+ )}
+ ItemSeparatorComponent={Divider}
+ renderItem={({ item }) => (
+
)}
- ItemSeparatorComponent={Divider}
- renderItem={({ item }) => (
-
- )}
- keyExtractor={(item) => item.postId.toString()}
- bouncesZoom
- bounces
- style={{
- backgroundColor: colors.background,
- }}
- refreshControl={
-
- }
- />
- )}
+ keyExtractor={(item) => item.postId.toString()}
+ bouncesZoom
+ bounces
+ style={{
+ backgroundColor: colors.background,
+ }}
+ refreshControl={
+
+ }
+ />
+ )}
+
);
});
diff --git a/src/app/Screens/Messages/index.tsx b/src/app/Screens/Messages/ChatList.tsx
similarity index 75%
rename from src/app/Screens/Messages/index.tsx
rename to src/app/Screens/Messages/ChatList.tsx
index 450d5dd..85e05b7 100644
--- a/src/app/Screens/Messages/index.tsx
+++ b/src/app/Screens/Messages/ChatList.tsx
@@ -1,6 +1,6 @@
import { RouteProp } from "@react-navigation/core";
import { StackNavigationProp } from "@react-navigation/stack";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import {
ActivityIndicator,
FlatList,
@@ -14,23 +14,22 @@ import { TouchableOpacity } from "react-native-gesture-handler";
import { Appbar, Caption, Divider, Text, useTheme } from "react-native-paper";
import { UserAvatar } from "../../Components/UserAvatar";
import { MessageStackNavigationParams } from "../../types/navigation";
-import useMessageList, { MessageList } from "../../utils/useMessageList";
+import useChatList, { ChatList } from "../../utils/useChatList";
+import useUser from "../../utils/useUser";
import { getTimeDistance } from "../../utils/utils";
type Props = {
- route: RouteProp;
- navigation: StackNavigationProp<
- MessageStackNavigationParams,
- "MessageList"
- >;
+ route: RouteProp;
+ navigation: StackNavigationProp;
};
type UserItemProps = {
- item: MessageList;
+ item: ChatList;
openMessage: (username: string) => void;
};
const UserListItem: React.FC = ({ item, openMessage }) => {
+ const { user } = useUser(item.username);
return (
= ({ item, openMessage }) => {
}}
onPress={() => openMessage(item.username)}
>
-
+
= ({ item, openMessage }) => {
const MessagesList: React.FC = ({ navigation, route }) => {
const { colors, dark } = useTheme();
const { height } = useWindowDimensions();
- const { messageList, loading, fetchMessageList } = useMessageList();
+ const { messageList, loading, fetchMessageList } = useChatList();
const [searchTerm, setSearchTerm] = useState("");
- const newMessage = () => {
- console.log("new");
- };
+ const newMessage = () => navigation.navigate("NewChat");
+
const goBack = () =>
route.params.rootNavigation
? route.params.rootNavigation.goBack()
@@ -90,6 +88,20 @@ const MessagesList: React.FC = ({ navigation, route }) => {
});
};
+ const [searchResults, setSearchResults] = useState();
+
+ useEffect(() => {
+ if (searchTerm.length > 0) {
+ setSearchResults(
+ messageList.filter((item) =>
+ item.username.includes(searchTerm.toLowerCase())
+ )
+ );
+ } else {
+ setSearchResults(null);
+ }
+ }, [messageList, searchTerm]);
+
return (
= ({ navigation, route }) => {
>
-
+
{loading && (
@@ -159,13 +171,17 @@ const MessagesList: React.FC = ({ navigation, route }) => {
Nothing to see here, yet.
}
- data={messageList
- .slice()
- .sort(
- (a, b) =>
- new Date(b.lastMessageAt).getTime() -
- new Date(a.lastMessageAt).getTime()
- )}
+ data={
+ searchResults
+ ? searchResults
+ : messageList
+ .slice()
+ .sort(
+ (a, b) =>
+ new Date(b.lastMessageAt).getTime() -
+ new Date(a.lastMessageAt).getTime()
+ )
+ }
ItemSeparatorComponent={Divider}
renderItem={({ item }) => (
diff --git a/src/app/Screens/Messages/NewChat.tsx b/src/app/Screens/Messages/NewChat.tsx
new file mode 100644
index 0000000..a154e19
--- /dev/null
+++ b/src/app/Screens/Messages/NewChat.tsx
@@ -0,0 +1,189 @@
+import { RouteProp } from "@react-navigation/core";
+import { StackNavigationProp } from "@react-navigation/stack";
+import React, { useContext, useEffect, useState } from "react";
+import {
+ ActivityIndicator,
+ FlatList,
+ RefreshControl,
+ StatusBar,
+ TextInput,
+ useWindowDimensions,
+ View,
+} from "react-native";
+import { Appbar, Divider, Text, useTheme } from "react-native-paper";
+import { Follower } from "../../store/FollowersStore";
+import { MessageStackNavigationParams } from "../../types/navigation";
+import { AppContext } from "../../utils/appContext";
+import useChatList, { ChatList } from "../../utils/useChatList";
+import useUser from "../../utils/useUser";
+import NewChatItem, { NewChatItemType } from "./NewChatItem";
+
+type Props = {
+ route: RouteProp;
+ navigation: StackNavigationProp;
+};
+
+export const NewChat: React.FC = ({ navigation }) => {
+ const { colors, dark } = useTheme();
+ const { height } = useWindowDimensions();
+ const { messageList, loading, fetchMessageList } = useChatList();
+ const [searchTerm, setSearchTerm] = useState("");
+
+ const { username: currentUsername } = useContext(AppContext);
+ const { following } = useUser(currentUsername);
+
+ const [chatList, setChatList] = useState([]);
+
+ const getUsersList = (chatsList: ChatList[], followingList: Follower[]) => {
+ const followingListData = followingList.map((user) => ({
+ username: user.following,
+ }));
+
+ const chatListData = [...chatsList, ...followingListData];
+ return [
+ ...new Map(
+ chatListData.map((item) => [item.username, item])
+ ).values(),
+ ];
+ };
+
+ const [searchResults, setSearchResults] = useState<
+ NewChatItemType[] | null
+ >();
+
+ useEffect(() => {
+ if (searchTerm.length > 0) {
+ setSearchResults(
+ messageList.filter((item) =>
+ item.username.includes(searchTerm.toLowerCase())
+ )
+ );
+ } else {
+ setSearchResults(null);
+ }
+ }, [messageList, searchTerm]);
+
+ useEffect(() => {
+ if (messageList && following)
+ setChatList(getUsersList(messageList, following));
+ }, [messageList, following]);
+
+ const goBack = () => navigation.goBack();
+
+ const openMessage = (username: string) => {
+ navigation.navigate("Messages", {
+ username,
+ });
+ };
+
+ useEffect(() => {
+ if (searchTerm.length > 0) {
+ setSearchResults(
+ messageList.filter((item) =>
+ item.username.includes(searchTerm.toLowerCase())
+ )
+ );
+ } else {
+ setSearchResults(null);
+ }
+ }, [messageList, searchTerm]);
+
+ return (
+
+
+
+
+
+
+
+ {loading && (
+
+
+
+ )}
+
+
+ setSearchTerm(text)}
+ style={{
+ flex: 1,
+ marginHorizontal: 16,
+ marginVertical: 16,
+ height: 40,
+ backgroundColor: "#3a3a3a",
+ borderRadius: 6,
+ paddingHorizontal: 16,
+ color: colors.text,
+ }}
+ />
+
+
+ Suggested
+
+ >
+ }
+ ListEmptyComponent={
+
+ Nothing to see here, yet.
+
+ }
+ data={searchResults ? searchResults : chatList.slice()}
+ ItemSeparatorComponent={Divider}
+ renderItem={({ item }) => (
+
+ )}
+ keyExtractor={(item) => item.username}
+ bouncesZoom
+ bounces
+ style={{
+ backgroundColor: colors.background,
+ }}
+ refreshControl={
+
+ }
+ />
+
+ );
+};
+
+export default NewChat;
diff --git a/src/app/Screens/Messages/NewChatItem.tsx b/src/app/Screens/Messages/NewChatItem.tsx
new file mode 100644
index 0000000..b71dd7b
--- /dev/null
+++ b/src/app/Screens/Messages/NewChatItem.tsx
@@ -0,0 +1,53 @@
+import React from "react";
+import { View } from "react-native";
+import { TouchableOpacity } from "react-native-gesture-handler";
+import { Caption, Text } from "react-native-paper";
+import { UserAvatar } from "../../Components/UserAvatar";
+import useUser from "../../utils/useUser";
+
+export type NewChatItemType = {
+ username: string;
+};
+
+type NewChatItemProps = {
+ item: NewChatItemType;
+ openMessage?: (username: string) => void;
+};
+
+const NewChatItem: React.FC = ({ item, openMessage }) => {
+ const { user } = useUser(item.username);
+ return (
+
+ openMessage
+ ? openMessage(item.username)
+ : console.log("no open message")
+ }
+ >
+
+
+
+ {item.username}
+
+ {user?.name}
+
+
+ );
+};
+
+export default NewChatItem;
diff --git a/src/app/Screens/Post/index.tsx b/src/app/Screens/Post/index.tsx
index 69fe110..34acdfb 100644
--- a/src/app/Screens/Post/index.tsx
+++ b/src/app/Screens/Post/index.tsx
@@ -1,9 +1,10 @@
import { RouteProp } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
-import React from "react";
+import React, { useState } from "react";
import { View } from "react-native";
import { Appbar, useTheme } from "react-native-paper";
import Post from "../../Components/Post";
+import PostBottomSheetWrapper from "../../Components/PostBottomSheetWrapper";
import { ExploreStackNavigationParams } from "../../types/navigation";
type Props = {
@@ -13,6 +14,29 @@ type Props = {
const PostDetail: React.FC = ({ route, navigation }) => {
const { colors } = useTheme();
+ const [openedModalData, setOpenedModalData] = useState<{
+ modalType: "MENU" | "SHARE";
+ username: string;
+ postId: number;
+ } | null>(null);
+
+ const closeModal = () => {
+ setOpenedModalData(null);
+ };
+ const onClose = () => setOpenedModalData(null);
+
+ const openModal = (
+ modalType: "MENU" | "SHARE",
+ username: string,
+ postId: number
+ ) => {
+ setOpenedModalData({
+ modalType,
+ postId: postId,
+ username: username,
+ });
+ };
+
return (
= ({ route, navigation }) => {
-
+
+
+
);
};
diff --git a/src/app/Screens/PostList/index.tsx b/src/app/Screens/PostList/index.tsx
index 3b36109..981c599 100644
--- a/src/app/Screens/PostList/index.tsx
+++ b/src/app/Screens/PostList/index.tsx
@@ -1,11 +1,12 @@
-import React, { useEffect, useRef } from "react";
+import React, { useEffect, useRef, useState } from "react";
import { Appbar, Divider, useTheme } from "react-native-paper";
import { FlatList, StatusBar, useWindowDimensions } from "react-native";
import { StackNavigationProp } from "@react-navigation/stack";
import { ProfileStackParams } from "../../types/navigation";
import { RouteProp } from "@react-navigation/native";
-import { PostContainer } from "../../Components/PostContainer";
import { definitions } from "../../types/supabase";
+import Post from "../../Components/Post";
+import PostBottomSheetWrapper from "../../Components/PostBottomSheetWrapper";
type Props = {
navigation: StackNavigationProp;
@@ -54,6 +55,29 @@ const PostList: React.FC = ({ navigation, route }) => {
};
};
+ const [openedModalData, setOpenedModalData] = useState<{
+ modalType: "MENU" | "SHARE";
+ username: string;
+ postId: number;
+ } | null>(null);
+
+ const closeModal = () => {
+ setOpenedModalData(null);
+ };
+ const onClose = () => setOpenedModalData(null);
+
+ const openModal = (
+ modalType: "MENU" | "SHARE",
+ username: string,
+ postId: number
+ ) => {
+ setOpenedModalData({
+ modalType,
+ postId: postId,
+ username: username,
+ });
+ };
+
return (
<>
= ({ navigation, route }) => {
- {route.params.postList && (
-
- new Date(b.postedAt).getTime() -
- new Date(a.postedAt).getTime()
- )}
- ItemSeparatorComponent={Divider}
- renderItem={({ item }) => }
- keyExtractor={(item) => item.postId.toString()}
- bouncesZoom
- bounces
- snapToAlignment={"start"}
- showsVerticalScrollIndicator
- style={{
- backgroundColor: colors.background,
- }}
- />
- )}
+
+
+ {route.params.postList && (
+
+ new Date(b.postedAt).getTime() -
+ new Date(a.postedAt).getTime()
+ )}
+ ItemSeparatorComponent={Divider}
+ renderItem={({ item }) => (
+
+ )}
+ keyExtractor={(item) => item.postId.toString()}
+ bouncesZoom
+ bounces
+ snapToAlignment={"start"}
+ showsVerticalScrollIndicator
+ style={{
+ backgroundColor: colors.background,
+ }}
+ />
+ )}
+
>
);
};
diff --git a/src/app/types/navigation.ts b/src/app/types/navigation.ts
index 4040ecb..88c9ff2 100644
--- a/src/app/types/navigation.ts
+++ b/src/app/types/navigation.ts
@@ -6,12 +6,13 @@ import { definitions } from "./supabase";
import { MaterialTopTabNavigationProp } from "@react-navigation/material-top-tabs";
export type MessageStackNavigationParams = {
- MessageList: {
+ ChatList: {
rootNavigation: MaterialTopTabNavigationProp<
SwipeTabNavigationParams,
"Messages"
>;
};
+ NewChat: undefined;
Messages: {
username: string;
};
@@ -50,7 +51,9 @@ export type HomeStackNavigationParams = {
>;
};
Comments: CommentsPageParams;
- Profile: StackNavigationProp;
+ Profile: ProfilePageProps & {
+ goBack: () => void;
+ };
};
export type ProfileStackParams = {
diff --git a/src/app/utils/appContext.tsx b/src/app/utils/appContext.tsx
index 0b151b8..0c265d0 100644
--- a/src/app/utils/appContext.tsx
+++ b/src/app/utils/appContext.tsx
@@ -3,6 +3,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
import { Theme } from "../types";
type ContextType = {
+ loading: boolean;
signupDone: boolean;
setSignupDone: (newState: boolean) => void;
@@ -28,6 +29,7 @@ type ContextType = {
};
export const AppContext = createContext({
+ loading: false,
signupDone: false,
setSignupDone: () => {},
@@ -56,6 +58,7 @@ type Props = {
children: React.ReactNode;
};
const AppContextProvider: React.FC = ({ children }) => {
+ const [loading, setLoading] = useState(true);
const [signupDone, setSignupDone] = useState(false);
const [username, setUsername] = useState(null);
const [name, setName] = useState(null);
@@ -100,9 +103,10 @@ const AppContextProvider: React.FC = ({ children }) => {
};
useEffect(() => {
- AsyncStorage.getItem("signupDone").then((res) =>
- setSignupDone(res === "true")
- );
+ AsyncStorage.getItem("signupDone").then((res) => {
+ setLoading(false);
+ setSignupDone(res === "true");
+ });
AsyncStorage.getItem("username").then((res) => setUsername(res));
AsyncStorage.getItem("email").then((res) => setEmail(res));
AsyncStorage.getItem("bio").then((res) => setBio(res));
@@ -128,6 +132,7 @@ const AppContextProvider: React.FC = ({ children }) => {
return (
void;
};
-const useMessageList = (): ReturnType => {
- const [messageList, setMessageList] = useState>([]);
+const useChatList = (): ReturnType => {
+ const [messageList, setMessageList] = useState>([]);
const [loading, setLoading] = useState(true);
const { username: currentUser } = useContext(AppContext);
- const getUsersList = (messageListData: Message[]) => {
- const users = messageListData.map((msg) => ({
- username: msg.sender === currentUser ? msg.receiver : msg.sender,
- messageType: msg.message_type,
- text: msg.text,
- lastMessageAt: msg.received_at,
- }));
-
- return [
- ...new Map(users.map((item) => [item.username, item])).values(),
- ];
- };
-
const messageListRef = useRef(messageList);
useEffect(() => {
messageListRef.current = messageList;
@@ -100,11 +88,14 @@ const useMessageList = (): ReturnType => {
}, [currentUser]);
const fetchMessageListData = async () => {
+ if (!currentUser)
+ return {
+ messageList: null,
+ };
try {
const messageListData = (await getMessageListFromDb()) || [];
-
return {
- messageList: getUsersList(messageListData),
+ messageList: getUsersList(messageListData, currentUser),
};
} catch (err) {
console.error("[fetchUserData]", err);
@@ -127,10 +118,10 @@ const useMessageList = (): ReturnType => {
new Date(b.received_at).getTime() -
new Date(a.received_at).getTime()
);
- if (messageListData) setMessageList(getUsersList(messageListData));
+ if (messageListData)
+ setMessageList(getUsersList(messageListData, currentUser));
setLoading(false);
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentUser]);
const fetchMessageList = async () => {
@@ -160,4 +151,4 @@ const useMessageList = (): ReturnType => {
};
};
-export default useMessageList;
+export default useChatList;
diff --git a/src/app/utils/useCurrentUser.tsx b/src/app/utils/useCurrentUser.tsx
index ae0ee7c..24a8b3a 100644
--- a/src/app/utils/useCurrentUser.tsx
+++ b/src/app/utils/useCurrentUser.tsx
@@ -51,7 +51,7 @@ const useCurrentUser = () => {
setCurrentUser(data);
}
- }, []);
+ }, [bio, email, name, profilePic, username]);
useEffect(() => {
if (email && username) {
diff --git a/src/app/utils/utils.ts b/src/app/utils/utils.ts
index 85256cd..e608849 100644
--- a/src/app/utils/utils.ts
+++ b/src/app/utils/utils.ts
@@ -1,7 +1,22 @@
import { format, formatDistanceToNow } from "date-fns";
+import { Message } from "../store/MessagesStore";
import { Post } from "../store/PostsStore";
import { definitions } from "../types/supabase";
+export const getUsersList = (
+ messageListData: Message[],
+ currentUser: string
+) => {
+ const users = messageListData.map((msg) => ({
+ username: msg.sender === currentUser ? msg.receiver : msg.sender,
+ messageType: msg.message_type,
+ text: msg.text,
+ lastMessageAt: msg.received_at,
+ }));
+
+ return [...new Map(users.map((item) => [item.username, item])).values()];
+};
+
export function uniqueList(
oldList: Model[],
newList: Model[],
diff --git a/yarn.lock b/yarn.lock
index 5810221..d91345a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1548,6 +1548,26 @@
lodash.isequal "^4.5.0"
react-native-redash "15.7.3"
+"@gorhom/bottom-sheet@^3":
+ version "3.6.3"
+ resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-3.6.3.tgz#461c8e419920f39f65a9ffa467f4406a890faee0"
+ integrity sha512-bFs7q29OyECsrT1GJUMgwBwGgT4OzH5640QWur5ZzJ73u3nQKxAVzGtbtc0JyGCbUPq66iXeQDyAwT3xNkG/iQ==
+ dependencies:
+ "@gorhom/portal" "^1.0.4"
+ invariant "^2.2.4"
+ lodash.isequal "^4.5.0"
+ nanoid "^3.1.20"
+ react-native-redash "^16.0.8"
+
+"@gorhom/portal@^1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@gorhom/portal/-/portal-1.0.4.tgz#643c7baaffee223819cdd99dad748c3d9dc6bd73"
+ integrity sha512-KalM9E6Op1TCzi00YKhczBz86EV0yYpWuRuTt5J4VS09O2gGbGAEEJwUKb/Rvr3XadtinpLjePj0tZ6lFVPp+g==
+ dependencies:
+ immer "^8.0.1"
+ lodash.isequal "^4.5.0"
+ nanoid "^3.1.20"
+
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -7040,6 +7060,11 @@ immer@8.0.1:
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==
+immer@^8.0.1:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a"
+ integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==
+
import-cwd@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@@ -11191,6 +11216,15 @@ react-native-redash@15.7.3:
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
+react-native-redash@^16.0.8:
+ version "16.0.11"
+ resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-16.0.11.tgz#973c3050a0645e3b9873790a8e68694770a32f27"
+ integrity sha512-9yTpeabkAoWASzmhEkynNh1wDTrLVtbE+x9dOV1DI23vRa7k1HI+Y1jOUNOPyRG2ddgHampNq9iHtsLnkK4mtw==
+ dependencies:
+ abs-svg-path "^0.1.1"
+ normalize-svg-path "^1.0.1"
+ parse-svg-path "^0.1.2"
+
react-native-safe-area-context@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.2.0.tgz#06113c6b208f982d68ab5c3cebd199ca93db6941"
@@ -11475,6 +11509,11 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
+reanimated-bottom-sheet@^1.0.0-alpha.22:
+ version "1.0.0-alpha.22"
+ resolved "https://registry.yarnpkg.com/reanimated-bottom-sheet/-/reanimated-bottom-sheet-1.0.0-alpha.22.tgz#01a200946f1a461f01f1e773e5b4961c2df2e53b"
+ integrity sha512-NxecCn+2iA4YzkFuRK5/b86GHHS2OhZ9VRgiM4q18AC20YE/psRilqxzXCKBEvkOjP5AaAvY0yfE7EkEFBjTvw==
+
recast@^0.20.3:
version "0.20.4"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.20.4.tgz#db55983eac70c46b3fff96c8e467d65ffb4a7abc"
@@ -13335,6 +13374,11 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
+use-memo-one@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
+ integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
+
use-subscription@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"