Skip to content

Commit

Permalink
feat: Remove pull to refresh indicator (#1448)
Browse files Browse the repository at this point in the history
* feat: Remove pull to refresh indicator

Removed indicator in iOS for pull to refresh
Added constants and comments
Removed unused nav list icon

* fixed typo
  • Loading branch information
alexrisch authored Jan 2, 2025
1 parent 79b18cf commit a0e545b
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 29 deletions.
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
// 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

0 comments on commit a0e545b

Please sign in to comment.