Skip to content

Commit

Permalink
feat: Added new profile page.
Browse files Browse the repository at this point in the history
  • Loading branch information
dohooo committed Sep 1, 2023
1 parent d8e578b commit d7029b5
Show file tree
Hide file tree
Showing 26 changed files with 436 additions and 339 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-yaks-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"xlog": patch
---

New profile page.
2 changes: 1 addition & 1 deletion src/components/ConnectEmailButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const ConnectEmailButton = (props: ButtonProps) => {
onPress={openWebPage}
>
<Stack paddingVertical="$3" borderRadius={"$5"} overflow="hidden">
<Center flex={1}>
<Center>
<XStack alignItems="center" gap="$2">
<Mail size={"$2"}/>
<Text fontWeight={"600"} color="$color" fontSize={"$6"}>{i18n.t("Connect with Email")}</Text>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConnectionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function ConnectBtn({ navigateToLogin }: { navigateToLogin: boolean }) {
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
/>
<Center flex={1}>
<Center>
<XStack alignItems="center" gap="$2">
<Wallet size={"$2"}/>
<Text fontWeight={"600"} color="$color" fontSize={"$6"}>{i18n.t("Connect with Wallet")}</Text>
Expand Down
3 changes: 2 additions & 1 deletion src/components/FeedList/FeedListItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export const FeedListItem: FC<Props> = (props) => {
{
characterId: note.characterId,
noteId: note.noteId,
coverImageIndex: placeholderBgIndex,
placeholderCoverImageIndex: placeholderBgIndex,
coverImage,
},
);
}, [note]);
Expand Down
140 changes: 15 additions & 125 deletions src/components/FeedList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,28 @@
import type { FC } from "react";
import { useEffect, useMemo, useRef } from "react";
import type { useAnimatedScrollHandler } from "react-native-reanimated";
import Animated from "react-native-reanimated";

import type { ContentStyle, MasonryFlashListProps, MasonryFlashListRef } from "@shopify/flash-list";
import { SizableText, Spinner, Stack, useWindowDimensions } from "tamagui";

import { useCharacterId } from "@/hooks/use-character-id";
import type { FeedType, SearchType } from "@/models/home.model";
import { useGetFeed } from "@/queries/home";
import type { ExpandedNote } from "@/types/crossbell";
import { debounce } from "@/utils/debounce";
import { GA } from "@/utils/GA";

import { FeedListItem } from "./FeedListItem";
import { Skeleton } from "./Skeleton";
import type { Props as FeedListProps } from "./useFeedList";
import { useFeedList } from "./useFeedList";

import topics from "../../data/topics.json";
import { Center } from "../Base/Center";
import { FillSpinner } from "../FillSpinner";
import { MasonryFlashList } from "../MasonryFlashList";
import { MeasureContainer } from "../utils/MeasureContainer";

export interface Props {
onScroll?: ReturnType<typeof useAnimatedScrollHandler>
type?: FeedType
noteIds?: string[]
/**
* @default 7
* */
daysInterval?: number
searchKeyword?: string
tag?: string
topic?: string
searchType?: SearchType
contentContainerStyle?: ContentStyle
}

export const FeedList: FC<Props> = (props) => {
const { type, searchType, searchKeyword, contentContainerStyle = {}, tag, topic, noteIds, daysInterval = 7, onScroll } = props;
const characterId = useCharacterId();
const gaLog = debounce(() => GA.logSearch({ search_term: searchKeyword }), 2000);
const { width } = useWindowDimensions();
const listRef = useRef<MasonryFlashListRef<ExpandedNote>>(null);
import { TabMasonryFlashList } from "../TabMasonryFlashList";

useEffect(() => {
typeof searchKeyword === "string" && gaLog();
}, [searchKeyword]);
interface MasonryProps extends FeedListProps {}

useEffect(() => {
listRef.current?.scrollToOffset({ offset: 0, animated: false });
}, [type, daysInterval]);
export const MasonryFeedList: FC<MasonryProps> = (props) => {
const _props = useFeedList(props);

const queryParams = useMemo(() => ({
type,
limit: 30,
characterId,
noteIds,
daysInterval,
searchKeyword,
searchType,
tag,
topicIncludeKeywords: topic
? topics.find(t => t.name === topic)?.includeKeywords
: undefined,
}), [
type,
characterId,
noteIds,
daysInterval,
searchKeyword,
searchType,
tag,
topic,
]);

// TODO
const feed = useGetFeed(queryParams);
const feedList = useMemo(() => (feed.data?.pages?.flatMap(page => page?.list) || []), [feed.data?.pages]);
return <MasonryFlashList<ExpandedNote> {..._props}/>;
};

return (
<MasonryFlashList<ExpandedNote>
data={feedList}
ref={listRef}
numColumns={2}
keyExtractor={post => `${post.characterId}-${post.noteId}`}
renderItem={({ item, index }) => (
<FeedListItem width={width / 2 - 12} key={index} note={item} searchKeyword={searchKeyword}/>
)}
ListEmptyComponent={(
<Stack>
{
feed.isFetching
? <Skeleton itemWidth={width / 2 - 12}/>
: (
<Center flex={1}>
<SizableText color={"$colorSubtitle"}>
There are no posts yet.
</SizableText>
</Center>
)
}
</Stack>
)}
bounces
estimatedItemSize={251}
ListFooterComponent={feed.isFetchingNextPage && <Spinner paddingBottom="$5"/>}
contentContainerStyle={{ ...contentContainerStyle, paddingHorizontal: 4 }}
scrollEventThrottle={16}
onScroll={onScroll}
onEndReachedThreshold={2}
onEndReached={() => {
if (
feedList.length === 0
|| feed.isFetchingNextPage
|| feed.hasNextPage === false
)
return;
interface TabMasonryProps extends FeedListProps {
index: number
characterId: number
}

GA.logEvent("feed_list_view", {
feed_length: feedList.length,
feed_type: queryParams.type,
query_limit: queryParams.limit,
character_id: queryParams.characterId,
note_ids: queryParams.noteIds,
days_interval: queryParams.daysInterval,
search_keyword: queryParams.searchKeyword,
search_type: queryParams.searchType,
tag: queryParams.tag,
topic_include_keywords: queryParams.topicIncludeKeywords,
});
export const TabMasonryFeedList: FC<TabMasonryProps> = (props) => {
const _props = useFeedList(props);

feed?.fetchNextPage?.();
}}
/>
);
return <TabMasonryFlashList<ExpandedNote> {..._props}/>;
};
141 changes: 141 additions & 0 deletions src/components/FeedList/useFeedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { FC } from "react";
import { useEffect, useMemo, useRef } from "react";
import type { useAnimatedScrollHandler } from "react-native-reanimated";

import type { ContentStyle, MasonryFlashListRef } from "@shopify/flash-list";
import { SizableText, Spinner, Stack, useWindowDimensions } from "tamagui";

import { useCharacterId } from "@/hooks/use-character-id";
import type { FeedType, SearchType } from "@/models/home.model";
import { useGetFeed } from "@/queries/home";
import type { ExpandedNote } from "@/types/crossbell";
import { debounce } from "@/utils/debounce";
import { GA } from "@/utils/GA";

import { FeedListItem } from "./FeedListItem";
import { Skeleton } from "./Skeleton";

import topics from "../../data/topics.json";
import { Center } from "../Base/Center";

export interface Props {
onScroll?: ReturnType<typeof useAnimatedScrollHandler>
type?: FeedType
noteIds?: string[]
/**
* @default 7
* */
daysInterval?: number
searchKeyword?: string
tag?: string
topic?: string
searchType?: SearchType
contentContainerStyle?: ContentStyle
characterId?: number
}

export const useFeedList = <T extends {}>(props: Props & T) => {
const { type, searchType, searchKeyword, contentContainerStyle = {}, tag, topic, noteIds, daysInterval = 7, onScroll, characterId, ...restProps } = props;
const _characterId = useCharacterId();
const gaLog = debounce(() => GA.logSearch({ search_term: searchKeyword }), 2000);
const { width } = useWindowDimensions();
const listRef = useRef<MasonryFlashListRef<ExpandedNote>>(null);

useEffect(() => {
typeof searchKeyword === "string" && gaLog();
}, [searchKeyword]);

useEffect(() => {
listRef.current?.scrollToOffset({ offset: 0, animated: false });
}, [type, daysInterval]);

const queryParams = useMemo(() => ({
type,
limit: 30,
characterId: characterId ?? _characterId,
noteIds,
daysInterval,
searchKeyword,
searchType,
tag,
topicIncludeKeywords: topic
? topics.find(t => t.name === topic)?.includeKeywords
: undefined,
}), [
type,
characterId,
_characterId,
noteIds,
daysInterval,
searchKeyword,
searchType,
tag,
topic,
]);

const feed = useGetFeed(queryParams);
const feedList = useMemo(() => (feed.data?.pages?.flatMap(page => page?.list) || []), [feed.data?.pages]);

return useMemo(() => ({
data: feedList,
ref: listRef,
numColumns: 2,
keyExtractor: post => `${post.characterId}-${post.noteId}`,
renderItem: ({ item, index }) => (
<FeedListItem width={width / 2 - 12} key={index} note={item} searchKeyword={searchKeyword}/>
),
ListEmptyComponent: <Stack>
{
feed.isFetching
? <Skeleton itemWidth={width / 2 - 12}/>
: (
<Center flex={1}>
<SizableText color={"$colorSubtitle"}>
There are no posts yet.
</SizableText>
</Center>
)
}
</Stack>,
bounces: true,
estimatedItemSize: 251,
ListFooterComponent: feed.isFetchingNextPage && <Spinner paddingBottom="$5"/>,
contentContainerStyle: { ...contentContainerStyle, paddingHorizontal: 4 },
scrollEventThrottle: 16,
onScroll,
onEndReachedThreshold: 2,
onEndReached: () => {
if (
feedList.length === 0
|| feed.isFetchingNextPage
|| feed.hasNextPage === false
)
return;

GA.logEvent("feed_list_view", {
feed_length: feedList.length,
feed_type: queryParams.type,
query_limit: queryParams.limit,
character_id: queryParams.characterId,
note_ids: queryParams.noteIds,
days_interval: queryParams.daysInterval,
search_keyword: queryParams.searchKeyword,
search_type: queryParams.searchType,
tag: queryParams.tag,
topic_include_keywords: queryParams.topicIncludeKeywords,
});

feed?.fetchNextPage?.();
},
...(restProps as T),
}), [
feed,
width,
contentContainerStyle,
onScroll,
restProps,
queryParams,
searchKeyword,
feedList,
]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export const width = Dimensions.get("window").width;
export const height = width / 1.58;

const bgs = [
require("../../assets/home-grid-bg/0.png"),
require("../../assets/home-grid-bg/1.png"),
require("../../assets/home-grid-bg/2.png"),
require("../assets/home-grid-bg/0.png"),
require("../assets/home-grid-bg/1.png"),
require("../assets/home-grid-bg/2.png"),
];

export const Background: FC<{
export const PolarLightBackground: FC<{
activeIndex: number
}> = ({ activeIndex }) => {
return (
Expand Down
33 changes: 33 additions & 0 deletions src/components/TabFlashList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";

import type { FlashListProps } from "@shopify/flash-list";
import { FlashList } from "@shopify/flash-list";

import { TabFlashListScrollView } from "./TabFlashListScrollView";

export type TabFlashListProps<T> = Omit<
FlashListProps<T>,
"renderScrollComponent"
> & {
index: number
};

function TabFlashListComponent<T>(
props: TabFlashListProps<T>,
ref: React.Ref<FlashList<T>>,
) {
return (
<FlashList
{...props}
renderScrollComponent={TabFlashListScrollView as any}
contentContainerStyle={{ paddingTop: 100 }}
ref={ref}
/>
);
}

export const TabFlashList = React.forwardRef(TabFlashListComponent) as <T>(
props: TabFlashListProps<T> & {
ref?: React.Ref<FlashList<T>>
}
) => React.ReactElement;
Loading

0 comments on commit d7029b5

Please sign in to comment.