@react-navigation/stack#HeaderBackButton TypeScript Examples

The following examples show how to use @react-navigation/stack#HeaderBackButton. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: CustomHeader.tsx    From lexicon with MIT License 4 votes vote down vote up
export function CustomHeader(props: Props) {
  const { colorScheme } = useColorScheme();
  const navigation = useNavigation();
  const styles = useStyles();
  const { colors, fontSizes, navHeader } = useTheme();

  const {
    title,
    color = 'background',
    rightTitle = '',
    rightIcon,
    onPressRight,
    noShadow = false,
    disabled = false,
    isLoading = false,
    prevScreen,
  } = props;

  const statusBarStyle: StatusBarStyle =
    colorScheme === 'light' ? 'dark' : 'light';

  const headerRight = isLoading ? (
    <ActivityIndicator style={styles.headerRight} />
  ) : (
    onPressRight && (
      <HeaderItem
        label={rightTitle}
        icon={rightIcon}
        onPressItem={onPressRight}
        disabled={disabled}
        style={styles.headerRight}
      />
    )
  );

  const routesLength = useNavigationState((state) => state.routes.length);

  const headerLeft =
    routesLength > 1 ? (
      <HeaderBackButton
        tintColor={isLoading ? colors.grey : colors.primary}
        style={isLoading && { opacity: 0.5 }}
        labelStyle={[
          {
            color: isLoading ? colors.grey : colors.primary,
            fontSize: fontSizes.m,
          },
        ]}
        disabled={isLoading}
        onPress={() => {
          prevScreen
            ? navigation.navigate(prevScreen, {
                backToTop: false,
              })
            : navigation.goBack();
        }}
      />
    ) : null;

  useLayoutEffect(() => {
    navigation.setOptions({
      title,
      ...navHeader,
      headerStyle: {
        backgroundColor: colors[color],
        ...(noShadow && { shadowOpacity: 0, elevation: 0 }),
      },
      headerLeft: () => headerLeft,
      headerRight: () => headerRight,
    });
  }, [
    color,
    colors,
    headerLeft,
    headerRight,
    navHeader,
    navigation,
    noShadow,
    title,
  ]);

  return <StatusBar style={statusBarStyle} />;
}
Example #2
Source File: PostDetail.tsx    From lexicon with MIT License 4 votes vote down vote up
export default function PostDetail() {
  const { topicsData } = usePost();
  const styles = useStyles();
  const { colors, spacing } = useTheme();

  const navigation = useNavigation<StackNavProp<'PostDetail'>>();
  const { navigate, goBack, setOptions, setParams } = navigation;

  const {
    params: {
      topicId,
      selectedChannelId = -1,
      postNumber = null,
      focusedPostNumber,
      prevScreen,
    },
  } = useRoute<StackRouteProp<'PostDetail'>>();

  const storage = useStorage();
  const currentUserId = storage.getItem('user')?.id;

  const channels = storage.getItem('channels');

  const virtualListRef = useRef<VirtualizedList<Post>>(null);

  const [hasOlderPost, setHasOlderPost] = useState(true);
  const [hasNewerPost, setHasNewerPost] = useState(true);
  const [loadingRefresh, setLoadingRefresh] = useState(false);
  const [loadingOlderPost, setLoadingOlderPost] = useState(false);
  const [loadingNewerPost, setLoadingNewerPost] = useState(false);
  const [loading, setLoading] = useState(true);
  const [canFlagFocusPost, setCanFlagFocusPost] = useState(false);
  const [canEditFocusPost, setCanEditFocusPost] = useState(false);
  const [showActionSheet, setShowActionSheet] = useState(false);
  const [replyLoading, setReplyLoading] = useState(false);
  const [postIdOnFocus, setPostIdOnFocus] = useState(0);
  const [startIndex, setStartIndex] = useState(0);
  const [endIndex, setEndIndex] = useState(0);
  const [stream, setStream] = useState<Array<number>>();
  const [fromPost, setFromPost] = useState(false);
  const [author, setAuthor] = useState('');
  const [showOptions, setShowOptions] = useState(false);
  const [flaggedByCommunity, setFlaggedByCommunity] = useState(false);
  const [content, setContent] = useState('');
  const [images, setImages] = useState<Array<string>>();
  const [mentionedUsers, setmentionedUsers] = useState<Array<string>>();
  const [isHidden, setHidden] = useState(false);

  const ios = Platform.OS === 'ios';

  useEffect(() => {
    if (selectedChannelId !== -1) {
      setOptions({
        headerLeft: () => (
          <HeaderBackButton
            onPress={goBack}
            style={{
              paddingBottom: ios ? spacing.l : spacing.s,
            }}
            tintColor={colors.primary}
          />
        ),
      });
    }
  }, [colors, goBack, ios, selectedChannelId, setOptions, spacing]);

  const {
    data,
    loading: topicDetailLoading,
    error,
    refetch,
    fetchMore,
  } = useTopicDetail(
    {
      variables: { topicId, postPointer: postNumber },
    },
    'HIDE_ALERT',
  );

  const { postRaw } = usePostRaw({
    onCompleted: ({ postRaw: { raw, listOfCooked, listOfMention } }) => {
      setContent(raw);
      setImages(listOfCooked);
      setmentionedUsers(listOfMention);
    },
    onError: () => {},
  });

  let postDetailContentHandlerResult = useMemo(() => {
    if (!data) {
      return;
    }
    return postDetailContentHandler({
      topicDetailData: data.topicDetail,
      channels,
    });
  }, [data, channels]);

  let topic = postDetailContentHandlerResult?.topic;

  let posts = postDetailContentHandlerResult?.posts;

  useEffect(() => {
    let isItemValid = false;
    if (!!(topic && topic.canEditTopic)) {
      isItemValid = true;
    }
    if (!!(posts && posts[0].canFlag && !posts[0].hidden)) {
      isItemValid = true;
    }
    setShowOptions(isItemValid);
  }, [topic, posts]);

  useEffect(() => {
    const topicData = topicsData.find(
      (topicData) => topicData?.topicId === topicId,
    );
    setContent(topicData?.content || '');
    setHidden(topicData?.hidden || false);
    if (posts) {
      postRaw({ variables: { postId: posts[0].id } });
      setHidden(posts[0].hidden || false);
    }
  }, [posts, topicsData, topicId, postRaw]);

  const onPressViewIgnoredContent = () => {
    setHidden(false);
  };

  useEffect(() => {
    if (data) {
      let topicDetailData = data.topicDetail;
      let {
        stream: tempStream,
        firstPostIndex,
        lastPostIndex,
      } = postDetailContentHandler({ topicDetailData, channels });
      setStream(tempStream || []);
      setStartIndex(firstPostIndex);
      setEndIndex(lastPostIndex);
      setLoading(false);
      setReplyLoading(false);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data]);

  const refreshPost = async () => {
    setLoadingRefresh(true);

    refetch({ topicId }).then(({ data }) => {
      const {
        topicDetail: { postStream, title, categoryId, tags },
      } = data;
      const { stream, posts } = postStream;
      const [firstPostId] = stream ?? [0];
      const firstPost = posts.find(({ id }) => firstPostId === id);

      client.writeFragment({
        id: `Topic:${topicId}`,
        fragment: TOPIC_FRAGMENT,
        data: {
          title,
          excerpt: firstPost?.raw,
          imageUrl: firstPost?.listOfCooked || undefined,
          categoryId,
          tags,
        },
      });
      setLoadingRefresh(false);
    });
  };

  const loadOlderPost = async () => {
    if (loadingOlderPost || !hasOlderPost || !stream || topicDetailLoading) {
      return;
    }
    setLoadingOlderPost(true);
    let nextEndIndex = startIndex;
    let newDataCount = Math.min(10, stream.length - nextEndIndex);
    let nextStartIndex = Math.max(0, nextEndIndex - newDataCount);

    let nextPosts = stream.slice(nextStartIndex, nextEndIndex);
    if (!nextPosts.length) {
      return;
    }
    await fetchMore({
      variables: {
        topicId,
        posts: nextPosts,
      },
    });
    setStartIndex(nextStartIndex);
    setLoadingOlderPost(false);
  };

  const loadNewerPost = async () => {
    if (loadingNewerPost || !hasNewerPost || !stream || topicDetailLoading) {
      return;
    }
    setLoadingNewerPost(true);
    let nextStartIndex = endIndex + 1;
    let newDataCount = Math.min(10, stream.length - nextStartIndex);
    let nextEndIndex = nextStartIndex + newDataCount;

    let nextPosts = stream.slice(nextStartIndex, nextEndIndex);
    if (!nextPosts.length) {
      return;
    }
    await fetchMore({
      variables: {
        topicId,
        posts: nextPosts,
      },
    });
    setEndIndex(nextEndIndex - 1);
    setLoadingNewerPost(false);
  };

  useTopicTiming(topicId, startIndex, stream);

  useEffect(() => {
    if (!stream || !posts) {
      return;
    }
    setHasOlderPost(stream[0] !== posts[0].id);
    setHasNewerPost(stream[stream.length - 1] !== posts[posts.length - 1].id);
  }, [stream, topicId, posts]);

  useEffect(() => {
    async function refetchData() {
      let index = 0;
      if (focusedPostNumber === 1) {
        await refetch({ topicId });
      } else {
        const result = await refetch({
          topicId,
          postPointer: focusedPostNumber,
        });
        let {
          data: {
            topicDetail: { postStream },
          },
        } = result;
        const firstPostIndex =
          postStream.stream?.findIndex(
            (postId) => postId === postStream.posts[0].id,
          ) || 0;
        let pointerToIndex = focusedPostNumber
          ? focusedPostNumber - 1 - firstPostIndex
          : 0;
        index = Math.min(MAX_DEFAULT_LOADED_POST_COUNT, pointerToIndex);
      }
      setTimeout(() => {
        try {
          virtualListRef.current &&
            virtualListRef.current.scrollToIndex({
              index,
              animated: true,
            });
        } catch {
          virtualListRef.current && virtualListRef.current.scrollToEnd();
        }
      }, 500);
    }
    const unsubscribe = navigation.addListener('focus', () => {
      if (focusedPostNumber != null && prevScreen === 'PostPreview') {
        setParams({ prevScreen: '' });
        setReplyLoading(true);
        refetchData();
      }
    });
    return unsubscribe;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [prevScreen, focusedPostNumber]);

  useEffect(() => {
    if (virtualListRef.current && postNumber) {
      if (posts?.slice(1).length !== 0) {
        virtualListRef.current.scrollToIndex({
          animated: true,
          index: postNumber > 1 ? postNumber - 2 - startIndex : 0,
        });
      }
    }
  }, [virtualListRef.current?.state]); // eslint-disable-line

  if (error) {
    return <LoadingOrError message={errorHandler(error)} />;
  }

  const onPressAuthor = (username: string) => {
    navigate('UserInformation', { username });
  };

  const renderTopicFromCache = () => {
    const cacheTopic = client.readFragment({
      id: `Topic:${topicId}`,
      fragment: TOPIC_FRAGMENT,
    });

    if (cacheTopic) {
      const cacheUser = client.readFragment({
        id: `UserIcon:${cacheTopic.authorUserId}`,
        fragment: USER_FRAGMENT,
      });

      const cacheFreqPoster = cacheTopic.posters?.map(
        (item: { userId: number }) => {
          let user = client.readFragment({
            id: `UserIcon:${item.userId}`,
            fragment: USER_FRAGMENT,
          });
          const { id, username, avatar } = user;
          return {
            id,
            username,
            avatar: getImage(avatar),
          };
        },
      );

      const channels = storage.getItem('channels');
      const channel = channels?.find(
        (channel) => channel.id === cacheTopic.categoryId,
      );

      const { title, excerpt, hidden, imageUrl, tags } = cacheTopic;
      const { username, avatar } = cacheUser;
      let tempPost = {
        id: 0,
        topicId,
        title,
        content: excerpt,
        hidden,
        username,
        avatar: getImage(avatar),
        images: [imageUrl] ?? undefined,
        viewCount: 0,
        replyCount: 0,
        likeCount: 0,
        isLiked: false,
        channel: channel ?? DEFAULT_CHANNEL,
        tags,
        createdAt: '',
        freqPosters: cacheFreqPoster ?? [],
      };

      return (
        <PostItem
          data={tempPost}
          postList={false}
          nonclickable
          onPressAuthor={onPressAuthor}
          content={content}
          isHidden={isHidden}
        />
      );
    }
    return null;
  };

  if (!data || !topic) {
    if (loading) {
      return renderTopicFromCache() ?? <LoadingOrError loading />;
    }

    return <LoadingOrError message={t('Post is not available')} />;
  }

  if (replyLoading) {
    return <LoadingOrError message={t('Finishing your Reply')} loading />;
  }

  const navToFlag = (
    postId = postIdOnFocus,
    isPost = fromPost,
    flaggedAuthor = author,
  ) => {
    navigate('FlagPost', { postId, isPost, flaggedAuthor });
  };

  const navToPost = (postId = postIdOnFocus) => {
    if (!topic) {
      return;
    }
    const {
      firstPostId,
      id,
      title,
      selectedChanelId: selectedChannelId,
      selectedTag: selectedTagsIds,
    } = topic;
    if (stream && postId === stream[0]) {
      navigate('NewPost', {
        editPostId: firstPostId,
        editTopicId: id,
        selectedChannelId,
        selectedTagsIds,
        oldContent: getPost(firstPostId)?.content,
        oldTitle: title,
        oldChannel: selectedChannelId,
        oldTags: selectedTagsIds,
        editedUser: {
          username: getPost(postId)?.username || '',
          avatar: getPost(postId)?.avatar || '',
        },
      });
    } else {
      navigate('PostReply', {
        topicId,
        title,
        editPostId: postId,
        oldContent: getPost(postId)?.content,
        focusedPostNumber: (getPost(postId)?.postNumber || 2) - 1,
        editedUser: {
          username: getPost(postId)?.username || '',
          avatar: getPost(postId)?.avatar || '',
        },
      });
    }
  };

  const onPressMore = (
    id?: number,
    canFlag = !!(posts && posts[0].canFlag),
    canEdit = !!(topic && topic.canEditTopic),
    flaggedByCommunity = !!(posts && posts[0].hidden),
    fromPost = true,
    author?: string,
  ) => {
    if (currentUserId && topic) {
      if (!id || typeof id !== 'number') {
        id = topic.firstPostId;
      }
      setPostIdOnFocus(id);
      setCanEditFocusPost(canEdit);
      setCanFlagFocusPost(canFlag);
      setFlaggedByCommunity(flaggedByCommunity);
      setShowActionSheet(true);
      if (author) {
        setAuthor(author);
      }
      if (!fromPost) {
        setFromPost(false);
      } else {
        setFromPost(true);
      }
    } else {
      errorHandlerAlert(LoginError, navigate);
    }
  };

  const actionItemOptions = () => {
    let options: ActionSheetProps['options'] = [];
    ios && options.push({ label: t('Cancel') });
    canEditFocusPost && options.push({ label: t('Edit Post') });
    !flaggedByCommunity &&
      options.push({
        label: canFlagFocusPost ? t('Flag') : t('Flagged'),
        disabled: !canFlagFocusPost,
      });
    return options;
  };

  const actionItemOnPress = (btnIndex: number) => {
    switch (btnIndex) {
      case 0: {
        return canEditFocusPost ? navToPost() : navToFlag();
      }
      case 1: {
        return canEditFocusPost && !flaggedByCommunity && navToFlag();
      }
    }
  };

  const getPost = (postId?: number) => {
    if (!posts) {
      return;
    }
    return postId === -1 || postIdOnFocus === -1
      ? undefined
      : posts.find(({ id }) => id === (postId || postIdOnFocus)) || posts[0];
  };

  const getReplyPost = (postNumberReplied: number) => {
    if (!posts) {
      return;
    }
    return posts.find(({ postNumber }) => postNumberReplied === postNumber);
  };

  const onPressReply = (id = -1) => {
    if (currentUserId) {
      if (stream && topic) {
        navigate('PostReply', {
          topicId,
          title: topic.title,
          post: getPost(id),
          focusedPostNumber: stream.length,
        });
      }
    } else {
      errorHandlerAlert(LoginError, navigate);
    }
  };

  let arrayCommentLikeCount = posts
    ? posts.slice(1).map((val) => {
        return val.likeCount;
      })
    : [];

  let sumCommentLikeCount = arrayCommentLikeCount.reduce((a, b) => a + b, 0);

  let currentPost = topicsData.filter((val) => {
    return val.topicId === topicId;
  });

  const getItem = (data: Array<Post>, index: number) => data[index];

  const getItemCount = (data: Array<Post>) => data.length;

  const keyExtractor = ({ id }: Post) => `post-${id}`;

  const renderItem = ({ item }: PostReplyItem) => {
    const { replyToPostNumber, canEdit, canFlag, hidden, id, username } = item;
    const replyPost =
      replyToPostNumber !== -1
        ? getReplyPost(replyToPostNumber ?? 1)
        : undefined;
    let isItemValid = false;
    if (canEdit) {
      isItemValid = true;
    }
    if (canFlag && !hidden) {
      isItemValid = true;
    }

    return (
      <NestedComment
        data={item}
        replyTo={replyPost}
        key={id}
        style={styles.lowerContainer}
        showOptions={isItemValid}
        onPressReply={() => onPressReply(id)}
        onPressMore={() =>
          onPressMore(id, canFlag, canEdit, hidden, false, username)
        }
        onPressAuthor={onPressAuthor}
      />
    );
  };

  const onScrollHandler = ({ index }: OnScrollInfo) => {
    setTimeout(
      () =>
        virtualListRef.current?.scrollToIndex({
          animated: true,
          index,
        }),
      50,
    );
  };

  return (
    <>
      <SafeAreaView style={styles.container}>
        {showOptions && (
          <CustomHeader
            title=""
            rightIcon="More"
            onPressRight={onPressMore}
            noShadow
          />
        )}
        <VirtualizedList
          ref={virtualListRef}
          refreshControl={
            <RefreshControl
              refreshing={
                (loadingRefresh || topicDetailLoading) && !loadingNewerPost
              }
              onRefresh={refreshPost}
              tintColor={colors.primary}
            />
          }
          data={posts && posts.slice(1)}
          getItem={getItem}
          getItemCount={getItemCount}
          renderItem={renderItem}
          keyExtractor={keyExtractor}
          initialNumToRender={5}
          maxToRenderPerBatch={7}
          windowSize={10}
          ListHeaderComponent={
            posts && stream && posts[0].id === stream[0] && topic ? (
              <PostItem
                data={{
                  ...posts[0],
                  likeCount:
                    currentPost.length !== 0
                      ? currentPost[0].likeCount - sumCommentLikeCount
                      : 0,
                  content: handleSpecialMarkdown(posts[0].content),
                  title: topic.title,
                  tags: topic.selectedTag,
                  viewCount: topic.viewCount,
                  replyCount: topic.replyCount,
                }}
                postList={false}
                nonclickable
                onPressAuthor={onPressAuthor}
                content={content}
                images={images}
                mentionedUsers={mentionedUsers}
                isHidden={isHidden}
                onPressViewIgnoredContent={onPressViewIgnoredContent}
              />
            ) : (
              renderTopicFromCache()
            )
          }
          onRefresh={hasOlderPost ? loadOlderPost : refreshPost}
          refreshing={
            (loadingRefresh || loadingOlderPost || topicDetailLoading) &&
            !loadingNewerPost
          }
          onEndReachedThreshold={0.1}
          onEndReached={loadNewerPost}
          ListFooterComponent={
            <FooterLoadingIndicator isHidden={!hasNewerPost} />
          }
          style={styles.scrollViewContainer}
          initialScrollIndex={0}
          onScrollToIndexFailed={onScrollHandler}
        />
        <TouchableOpacity
          style={styles.inputCommentContainer}
          onPress={() => onPressReply(-1)}
        >
          <Text style={styles.inputComment}>{t('Write your reply here')}</Text>
        </TouchableOpacity>
      </SafeAreaView>
      <TouchableOpacity>
        {stream && (
          <ActionSheet
            visible={showActionSheet}
            options={actionItemOptions()}
            cancelButtonIndex={ios ? 0 : undefined}
            actionItemOnPress={actionItemOnPress}
            onClose={() => {
              setShowActionSheet(false);
            }}
            style={!ios && styles.androidModalContainer}
          />
        )}
      </TouchableOpacity>
    </>
  );
}