Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Remove pull to refresh indicator #1448

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Platform } from "react-native";

// iOS has it's own bounce and search bar, so we need to set a different threshold
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the comment - it's should be its since this is the possessive form. The current text reads:

// iOS has it's own bounce and search bar

but should be:

// iOS has its own bounce and search bar

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

// Android does not have a bounce, so this will never really get hit.
export const CONVERSATION_FLASH_LIST_REFRESH_THRESHOLD =
Platform.OS === "ios" ? -190 : 0;
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,38 @@ import { FlashList } from "@shopify/flash-list";
import { backgroundColor } from "@styles/colors";
import { ConversationListContext } from "@utils/conversationList";
import { useCallback, useEffect, useRef } from "react";
import { Platform, StyleSheet, View, useColorScheme } from "react-native";
import {
NativeScrollEvent,
NativeSyntheticEvent,
Platform,
StyleSheet,
View,
useColorScheme,
} from "react-native";

import HiddenRequestsButton from "./ConversationList/HiddenRequestsButton";
import { V3GroupConversationListItem } from "./V3GroupConversationListItem";
import { useChatStore, useCurrentAccount } from "../data/store/accountsStore";
import { useSelect } from "../data/store/storeHelpers";
import { NavigationParamList } from "../screens/Navigation/Navigation";
import { ConversationFlatListHiddenRequestItem } from "../utils/conversation";
import { FlatListItemType } from "../features/conversation-list/ConversationList.types";
import HiddenRequestsButton from "../ConversationList/HiddenRequestsButton";
import { V3GroupConversationListItem } from "../V3GroupConversationListItem";
import { useChatStore, useCurrentAccount } from "@data/store/accountsStore";
import { useSelect } from "@data/store/storeHelpers";
import { NavigationParamList } from "@screens/Navigation/Navigation";
import { ConversationFlatListHiddenRequestItem } from "@utils/conversation";
import { FlatListItemType } from "@features/conversation-list/ConversationList.types";
import { unwrapConversationContainer } from "@utils/groupUtils/conversationContainerHelpers";
import { ConversationVersion } from "@xmtp/react-native-sdk";
import {
DmWithCodecsType,
GroupWithCodecsType,
} from "@/utils/xmtpRN/client.types";
import { V3DMListItem } from "./V3DMListItem";
import { V3DMListItem } from "../V3DMListItem";
import { CONVERSATION_FLASH_LIST_REFRESH_THRESHOLD } from "./ConversationFlashList.constants";

type Props = {
onScroll?: () => void;
items: FlatListItemType[];
itemsForSearchQuery?: string;
ListHeaderComponent?: React.ReactElement | null;
ListFooterComponent?: React.ReactElement | null;
refetch?: () => void;
refetch?: () => Promise<unknown>;
isRefetching?: boolean;
} & NativeStackScreenProps<
NavigationParamList,
Expand Down Expand Up @@ -61,6 +69,7 @@ export default function ConversationFlashList({
);
const userAddress = useCurrentAccount() as string;
const listRef = useRef<FlashList<any> | undefined>();
const refreshingRef = useRef(false);

const renderItem = useCallback(({ item }: { item: FlatListItemType }) => {
if ("lastMessage" in item) {
Expand All @@ -87,6 +96,34 @@ export default function ConversationFlashList({
return null;
}, []);

const handleRefresh = useCallback(async () => {
if (refreshingRef.current) return;
refreshingRef.current = true;
try {
console.log("refetching from pull");
await refetch?.();
} catch (error) {
console.error(error);
} finally {
refreshingRef.current = false;
}
}, [refetch]);

const onScrollList = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
if (refreshingRef.current) return;
// On Android the list does not bounce, so this will only get hit
// on iOS when the user scrolls up
if (
e.nativeEvent.contentOffset.y <
CONVERSATION_FLASH_LIST_REFRESH_THRESHOLD
) {
handleRefresh();
}
},
[handleRefresh]
);

return (
<ConversationListContext.Provider
value={{
Expand All @@ -98,11 +135,12 @@ export default function ConversationFlashList({
<View style={styles.container}>
<View style={styles.conversationList}>
<FlashList
onRefresh={refetch}
refreshing={isRefetching}
onRefresh={Platform.OS === "android" ? refetch : undefined}
refreshing={Platform.OS === "android" ? isRefetching : undefined}
keyboardShouldPersistTaps="handled"
onMomentumScrollBegin={onScroll}
onScrollBeginDrag={onScroll}
onScroll={onScrollList}
alwaysBounceVertical={items.length > 0}
contentInsetAdjustmentBehavior="automatic"
data={items}
Expand Down
6 changes: 3 additions & 3 deletions features/blocked-chats/ConversationBlockedListNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
NativeStack,
navigationAnimation,
NavigationParamList,
} from "../../screens/Navigation/Navigation";
import AndroidBackAction from "../../components/AndroidBackAction";
import ConversationFlashList from "../../components/ConversationFlashList";
} from "@screens/Navigation/Navigation";
import AndroidBackAction from "@components/AndroidBackAction";
import ConversationFlashList from "@/components/ConversationFlashList/ConversationFlashList";
import { translate } from "@i18n/index";
import { useAllBlockedChats } from "./useAllBlockedChats";

Expand Down
6 changes: 6 additions & 0 deletions features/conversation/conversation-list.contstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Platform } from "react-native";

// On iOS the list has a bounce, so we need to set a different threshold
// to trigger the refresh.
export const CONVERSATION_LIST_REFRESH_THRESHOLD =
Platform.OS === "ios" ? -120 : 0;
32 changes: 31 additions & 1 deletion features/conversation/conversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ import {
ConversationStoreProvider,
useCurrentConversationTopic,
} from "./conversation.store-context";
import { CONVERSATION_LIST_REFRESH_THRESHOLD } from "./conversation-list.contstants";
import {
NativeScrollEvent,
NativeSyntheticEvent,
Platform,
} from "react-native";

export const Conversation = memo(function Conversation(props: {
topic: ConversationTopic;
Expand Down Expand Up @@ -186,6 +192,8 @@ const Messages = memo(function Messages(props: {
const { data: currentAccountInboxId } = useCurrentAccountInboxId();
const topic = useCurrentConversationTopic()!;

const refreshingRef = useRef(false);

const {
data: messages,
isLoading: messagesLoading,
Expand Down Expand Up @@ -223,11 +231,33 @@ const Messages = memo(function Messages(props: {
}
}, [isUnread, messagesLoading, toggleReadStatus]);

const handleRefresh = useCallback(async () => {
try {
refreshingRef.current = true;
await refetch();
} catch (e) {
console.error(e);
} finally {
refreshingRef.current = false;
}
}, [refetch]);

const onScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
if (refreshingRef.current && !isRefetchingMessages) return;
if (e.nativeEvent.contentOffset.y < CONVERSATION_LIST_REFRESH_THRESHOLD) {
handleRefresh();
}
},
[handleRefresh, isRefetchingMessages]
);

return (
<ConversationMessagesList
messageIds={messages?.ids ?? []}
refreshing={isRefetchingMessages}
onRefresh={refetch}
onRefresh={Platform.OS === "android" ? refetch : undefined}
onScroll={onScroll}
ListEmptyComponent={
isConversationDm(conversation) ? (
<DmConversationEmpty />
Expand Down
2 changes: 1 addition & 1 deletion screens/ConversationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { gestureHandlerRootHOC } from "react-native-gesture-handler";
import { SearchBarCommands } from "react-native-screens";

import ChatNullState from "../components/ConversationList/ChatNullState";
import ConversationFlashList from "../components/ConversationFlashList";
import ConversationFlashList from "../components/ConversationFlashList/ConversationFlashList";
import NewConversationButton from "../components/ConversationList/NewConversationButton";
import RequestsButton from "../components/ConversationList/RequestsButton";
import EphemeralAccountBanner from "../components/EphemeralAccountBanner";
Expand Down
3 changes: 0 additions & 3 deletions screens/Navigation/ConversationListNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ export default function ConversationListNav() {
placeholder={searchPlaceholder()}
onChangeText={onChangeSearch}
value={searchQuery}
icon={({ color }) => (
<Picto picto="menu" size={PictoSizes.navItem} color={color} />
)}
mode="bar"
autoCapitalize="none"
autoFocus={false}
Expand Down
2 changes: 1 addition & 1 deletion screens/Navigation/ConversationRequestsListNav.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "./Navigation";
import ActivityIndicator from "../../components/ActivityIndicator/ActivityIndicator";
import Button from "../../components/Button/Button";
import ConversationFlashList from "../../components/ConversationFlashList";
import ConversationFlashList from "@/components/ConversationFlashList/ConversationFlashList";
import { showActionSheetWithOptions } from "../../components/StateHandlers/ActionSheetStateHandler";
import { useCurrentAccount } from "../../data/store/accountsStore";
import { consentToAddressesOnProtocolByAccount } from "../../utils/xmtpRN/contacts";
Expand Down
16 changes: 8 additions & 8 deletions screens/Navigation/ConversationRequestsListNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import {
navigationAnimation,
NavigationParamList,
} from "./Navigation";
import AndroidBackAction from "../../components/AndroidBackAction";
import Button from "../../components/Button/Button";
import ConversationFlashList from "../../components/ConversationFlashList";
import { showActionSheetWithOptions } from "../../components/StateHandlers/ActionSheetStateHandler";
import { useCurrentAccount } from "../../data/store/accountsStore";
import { consentToAddressesOnProtocolByAccount } from "../../utils/xmtpRN/contacts";
import { useRequestItems } from "../../features/conversation-requests-list/useRequestItems";
import { FlatListItemType } from "../../features/conversation-list/ConversationList.types";
import AndroidBackAction from "@components/AndroidBackAction";
import Button from "@components/Button/Button";
import ConversationFlashList from "@/components/ConversationFlashList/ConversationFlashList";
import { showActionSheetWithOptions } from "@components/StateHandlers/ActionSheetStateHandler";
import { useCurrentAccount } from "@data/store/accountsStore";
import { consentToAddressesOnProtocolByAccount } from "@utils/xmtpRN/contacts";
import { useRequestItems } from "@features/conversation-requests-list/useRequestItems";
import { FlatListItemType } from "@features/conversation-list/ConversationList.types";

// TODO: Remove iOS-specific code due to the existence of a .ios file
// TODO: Alternatively, implement an Android equivalent for the segmented controller
Expand Down
Loading