Skip to content

Commit

Permalink
feat: post feed into user profile (#81)
Browse files Browse the repository at this point in the history
* fix: reply to a post
* feat: view feed on user profile
* chore: fetch all posts
* chore: displaying user post
---------

Signed-off-by: Iuri Pereira <689440+iuricmp@users.noreply.github.com>
  • Loading branch information
iuricmp authored Jun 4, 2024
1 parent 95cb3ea commit 120f160
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 42 deletions.
7 changes: 7 additions & 0 deletions mobile/app/[account]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Following, User } from "@gno/types";
import { useAppSelector } from "@gno/redux";
import { selectAccount } from "redux/features/accountSlice";
import { setFollows } from "redux/features/profileSlice";
import { useFeed } from "@gno/hooks/use-feed";

export default function Page() {
const { accountName } = useLocalSearchParams<{ accountName: string }>();
Expand All @@ -16,8 +17,10 @@ export default function Page() {
const [user, setUser] = useState<User | undefined>(undefined);
const [following, setFollowing] = useState<Following[]>([]);
const [followers, setFollowers] = useState<Following[]>([]);
const [totalPosts, setTotalPosts] = useState<number>(0);

const navigation = useNavigation();
const feed = useFeed();
const search = useSearch();
const currentUser = useAppSelector(selectAccount);
const dispatch = useDispatch();
Expand All @@ -43,6 +46,9 @@ export default function Page() {
const { following } = await search.GetJsonFollowing(response.address);
setFollowing(following);

const total = await feed.fetchCount(response.address);
setTotalPosts(total);

dispatch(setFollows({ followers, following }));
} catch (error: unknown | Error) {
console.log(error);
Expand Down Expand Up @@ -78,6 +84,7 @@ export default function Page() {
return (
<AccountView
user={user}
totalPosts={totalPosts}
currentUser={currentUser}
following={following}
followers={followers}
Expand Down
34 changes: 26 additions & 8 deletions mobile/app/home/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ActivityIndicator, FlatList, Platform, StyleSheet, View, Alert } from "react-native";
import { ActivityIndicator, FlatList, Platform, StyleSheet, View, Alert as RNAlert } from "react-native";
import React, { useEffect, useRef, useState } from "react";
import { useNavigation, useRouter } from "expo-router";
import { useFeed } from "@gno/hooks/use-feed";
import Layout from "@gno/components/layout";
import useScrollToTop from "@gno/components/utils/useScrollToTopWithOffset";
import Button from "@gno/components/button";
import FeedView from "@gno/components/view/feed";
import { Post } from "@gno/types";
import { setPostToReply, useAppDispatch } from "@gno/redux";
import { selectAccount, setPostToReply, useAppDispatch, useAppSelector } from "@gno/redux";
import Alert from "@gno/components/alert";
import { FeedView } from "@gno/components/view";

export default function Page() {
const [totalPosts, setTotalPosts] = useState(0);
Expand All @@ -20,17 +21,23 @@ export default function Page() {
const ref = useRef<FlatList>(null);
const dispatch = useAppDispatch();

const user = useAppSelector(selectAccount);

useScrollToTop(ref, Platform.select({ ios: -150, default: 0 }));

useEffect(() => {
const unsubscribe = navigation.addListener("focus", async () => {
if (!user) {
RNAlert.alert("No user found.");
return;
}
setError(undefined);
setIsLoading(true);
try {
const total = await feed.fetchCount();
const total = await feed.fetchCount(user.address);
setTotalPosts(total);
} catch (error) {
Alert.alert("Error while fetching posts.", " " + error);
RNAlert.alert("Error while fetching posts.", " " + error);
console.error(error);
} finally {
setIsLoading(false);
Expand All @@ -43,8 +50,9 @@ export default function Page() {
router.push("/post");
};

const onPress = (item: Post) => {
dispatch(setPostToReply(item));
const onPress = async (item: Post) => {
const thread = await feed.fetchThread(item.user.address, Number(item.id));
await dispatch(setPostToReply({ post: item, thread: thread.data }));
router.navigate({ pathname: "/post/[post_id]", params: { post_id: item.id } });
};

Expand All @@ -64,10 +72,20 @@ export default function Page() {
</Layout.Container>
);

if (!user) {
return (
<Layout.Container>
<Layout.Body>
<Alert severity="error" message="No user found." />
</Layout.Body>
</Layout.Container>
);
}

return (
<Layout.Container>
<View style={styles.container}>
<FeedView totalPosts={totalPosts} onPress={onPress} />
<FeedView totalPosts={totalPosts} onPress={onPress} address={user.address} type="userFeed" />
<Button.TouchableOpacity title="Post" onPress={onPressPost} style={styles.post} variant="primary" />
</View>
</Layout.Container>
Expand Down
10 changes: 9 additions & 1 deletion mobile/app/post/[post_id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ function Page() {
// TODO: on press a tweet inside the reply thread
};

if (!post) return <Text.Body>Error on loading Post</Text.Body>;
if (!post) {
return (
<Layout.Container>
<Layout.Body>
<Alert severity="error" message="Error while fetching posts, please, check the logs." />
</Layout.Body>
</Layout.Container>
);
}

return (
<Layout.Container>
Expand Down
6 changes: 3 additions & 3 deletions mobile/components/layout/body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import styled from "styled-components/native";
export default styled.View`
width: 100%;
height: 100%;
padding-top: 10px;
padding-left: 12px;
padding-right: 12px;
padding-top: 12px;
padding-left: 8px;
padding-right: 8px;
`;

export const BodyAlignedBotton = styled.View`
Expand Down
19 changes: 18 additions & 1 deletion mobile/components/view/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Layout from "@gno/components/layout";
import { colors } from "@gno/styles/colors";
import Button from "@gno/components/button";
import { Following, User } from "@gno/types";
import FeedView from "../feed/feed-view";

interface Props {
onPressFollowing: () => void;
Expand All @@ -15,10 +16,21 @@ interface Props {
currentUser: User;
followers: Following[];
following: Following[];
totalPosts: number;
}

function AccountView(props: Props) {
const { onPressFollowing, onPressFollowers, onPressFollow, onPressUnfollow, user, following, followers, currentUser } = props;
const {
onPressFollowing,
onPressFollowers,
onPressFollow,
onPressUnfollow,
user,
following,
followers,
currentUser,
totalPosts,
} = props;
const accountName = user.name;

const isFollowed = useMemo(() => followers.find((f) => f.address === currentUser.address) != null, [user, followers]);
Expand Down Expand Up @@ -62,6 +74,11 @@ function AccountView(props: Props) {
</TouchableOpacity>
</View>
</View>
<View style={{ flex: 1, width: "100%", paddingHorizontal: 16, paddingTop: 8 }}>
<Text.Body>Posts</Text.Body>
<View style={{ height: 1, backgroundColor: colors.grayscale[200] }} />
<FeedView totalPosts={totalPosts} onPress={(e) => console.log(e)} address={user.address} type="userPosts" />
</View>
</View>
</Layout.Container>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import { Tweet } from "@gno/components/feed/tweet";
type Props = {
totalPosts: number;
onPress: (item: Post) => void;
address: string;
type: "userPosts" | "userFeed";
};

const subtractOrZero = (a: number, b: number) => Math.max(0, a - b);

export default function FeedView({ totalPosts, onPress }: Props) {
export default function FeedView({ totalPosts, onPress, address, type }: Props) {
const pageSize = 9;
const [startIndex, setStartIndex] = useState(subtractOrZero(totalPosts, pageSize));
const [endIndex, setEndIndex] = useState(totalPosts);
Expand All @@ -35,7 +37,7 @@ export default function FeedView({ totalPosts, onPress }: Props) {
const onRefresh = React.useCallback(async () => {
setRefreshing(true);

await fetchData();
await fetchData(address);

setRefreshing(false);
}, []);
Expand All @@ -44,15 +46,18 @@ export default function FeedView({ totalPosts, onPress }: Props) {
console.log("end reached", isEndReached);
if (!isEndReached) {
setIsEndReached(true);
fetchData();
fetchData(address);
}
};

const fetchData = async () => {
const fetchData = async (address: string) => {
setIsLoading(true);
try {
console.log("fetching data from %d to %d", startIndex, endIndex);
const result = await feed.fetchFeed(startIndex, endIndex);
const result =
type === "userPosts"
? await feed.fetchThreadPosts(address, startIndex, endIndex)
: await feed.fetchFeed(address, startIndex, endIndex);
setLimit(result.n_posts);
setStartIndex(subtractOrZero(startIndex, pageSize));
setEndIndex(startIndex);
Expand Down
2 changes: 1 addition & 1 deletion mobile/components/view/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AccountView from "./account";
import FeedView from "./feed";
import FeedView from "./feed/feed-view";

export { AccountView, FeedView };
22 changes: 11 additions & 11 deletions mobile/redux/features/replySlice.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { Post } from "@gno/types";
import { useFeed } from "@gno/hooks/use-feed";

export interface State {
postToReply: Post | undefined;
/** This thread belongs to the postToReply */
thread: Post[] | undefined;
/** This thread belongs to the postToReply */
thread: Post[] | undefined;
}

const initialState: State = {
postToReply: undefined,
thread: undefined,
thread: undefined,
};

export const setPostToReply = createAsyncThunk("post/reply", async (post: Post, _) => {
const { fetchThread } = useFeed();

const result = await fetchThread(post.user.address, Number(post.id));

return {post, thread: result.data};
export const setPostToReply = createAsyncThunk("post/reply", async ({ post, thread }: { post: Post; thread: Post[] }) => {
return { post, thread };
});

export const replySlice = createSlice({
Expand All @@ -32,7 +27,12 @@ export const replySlice = createSlice({
extraReducers(builder) {
builder.addCase(setPostToReply.fulfilled, (state, action) => {
state.postToReply = action.payload.post;
state.thread = action.payload.thread;
state.thread = action.payload.thread;
});
builder.addCase(setPostToReply.rejected, (state, action) => {
state.postToReply = undefined;
state.thread = undefined;
console.log("Error while replying a post, please, check the logs.", action.error.message);
});
},
});
Expand Down
33 changes: 21 additions & 12 deletions mobile/src/hooks/use-feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,38 @@ import useGnoJsonParser from "./use-gno-json-parser";
import { useIndexerContext } from "@gno/provider/indexer-provider";
import { Alert } from "react-native";

interface ThreadPosts {
data: Post[];
n_posts: number;
}

export const useFeed = () => {
const gno = useGnoNativeContext();
const cache = useUserCache();
const parser = useGnoJsonParser();
const indexer = useIndexerContext();

async function fetchThread(address: string, postId: number): Promise<{ data: Post[]; n_posts: number }> {
async function fetchThreadPosts(address: string, startIndex: number, endIndex: number): Promise<ThreadPosts> {
await checkActiveAccount();

const result = await gno.qEval("gno.land/r/berty/social", `GetThreadPosts("${address}",${postId},0, 0, 100)`);
const result = await gno.qEval("gno.land/r/berty/social", `GetThreadPosts("${address}",0, 0, ${startIndex}, ${endIndex})`);
const json = await enrichData(result);

return json;
}

async function fetchFeed(startIndex: number, endIndex: number): Promise<{ data: Post[]; n_posts: number }> {
const account = await checkActiveAccount();
async function fetchThread(address: string, postId: number): Promise<ThreadPosts> {
await checkActiveAccount();

const result = await gno.qEval("gno.land/r/berty/social", `GetThreadPosts("${address}",${postId},0, 0, 100)`);
const json = await enrichData(result);

return json;
}

async function fetchFeed(address: string, startIndex: number, endIndex: number): Promise<ThreadPosts> {
try {
const [nHomePosts, addrAndIDs] = await indexer.getHomePosts(account.address, BigInt(startIndex), BigInt(endIndex));
const [nHomePosts, addrAndIDs] = await indexer.getHomePosts(address, BigInt(startIndex), BigInt(endIndex));
const result = await gno.qEval("gno.land/r/berty/social", `GetJsonTopPostsByID(${addrAndIDs})`);
return await enrichData(result, nHomePosts);
} catch (error) {
Expand All @@ -35,7 +47,6 @@ export const useFeed = () => {

async function enrichData(result: string, nHomePosts?: number) {
const jsonResult = parser.toJson(result);

// If isThread then jsonResult is {n_threads: number, posts: array<{index: number, post: Post}>} from GetThreadPosts.
const isThread = "n_threads" in jsonResult;
const jsonPosts = isThread ? jsonResult.posts : jsonResult;
Expand Down Expand Up @@ -66,16 +77,14 @@ export const useFeed = () => {
}

return {
data: isThread ? posts : posts.reverse(),
data: posts.reverse(),
n_posts,
};
}

async function fetchCount() {
const account = await checkActiveAccount();

async function fetchCount(address: string) {
// Use a range of 0,0 to just get nHomePosts.
const [nHomePosts, _] = await indexer.getHomePosts(account.address, BigInt(0), BigInt(0));
const [nHomePosts, _] = await indexer.getHomePosts(address, BigInt(0), BigInt(0));
return nHomePosts;
}

Expand All @@ -90,5 +99,5 @@ export const useFeed = () => {
return user;
}

return { fetchFeed, fetchCount, fetchThread };
return { fetchFeed, fetchCount, fetchThread, fetchThreadPosts, checkActiveAccount };
};

0 comments on commit 120f160

Please sign in to comment.