diff --git a/.cursorrules b/.cursorrules index 83b988553..d5fadf204 100644 --- a/.cursorrules +++ b/.cursorrules @@ -5,11 +5,13 @@ - Write concise TypeScript code. - Use functional programming patterns. - Prefer clean, readable code over compact code, using empty lines to separate logical blocks and improve readability. -- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). +- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). Doesn't mater if it's long. For example this is good: "useGroupMembersInfoForCurrentAccount". - Prefer clean and easy to read code over compact code. - Don't create function(s) inside other functions unless it's specified - Add clear, concise comments to explain non-obvious logic or unconventional implementation decisions. - When refactoring code, preserve existing comments unless explicitly instructed otherwise or the related code is removed. Comments often explain important context or implementation decisions that should be maintained. +- Comments should be above the code they are describing. +- ## TypeScript diff --git a/assets/icons/chat-bubble-dark.png b/assets/icons/chat-bubble-dark.png new file mode 100644 index 000000000..fdc38d61c Binary files /dev/null and b/assets/icons/chat-bubble-dark.png differ diff --git a/assets/icons/chat-bubble-light.png b/assets/icons/chat-bubble-light.png new file mode 100644 index 000000000..3d78b17f3 Binary files /dev/null and b/assets/icons/chat-bubble-light.png differ diff --git a/components/AccountSettingsButton.tsx b/components/AccountSettingsButton.tsx index 8ddc2a79c..7c1577825 100644 --- a/components/AccountSettingsButton.tsx +++ b/components/AccountSettingsButton.tsx @@ -17,21 +17,17 @@ import { useColorScheme, } from "react-native"; -import { - useAccountsStore, - useErroredAccountsMap, -} from "../data/store/accountsStore"; +import { invalidateProfileSocialsQuery } from "@/queries/useProfileSocialsQuery"; +import { useAccountsStore } from "../data/store/accountsStore"; import { useAppStore } from "../data/store/appStore"; import { useSelect } from "../data/store/storeHelpers"; +import { NotificationPermissionStatus } from "../features/notifications/types/Notifications.types"; +import { requestPushNotificationsPermissions } from "../features/notifications/utils/requestPushNotificationsPermissions"; import { useRouter } from "../navigation/useNavigation"; import { navigate } from "../utils/navigation"; -import { requestPushNotificationsPermissions } from "../features/notifications/utils/requestPushNotificationsPermissions"; import Picto from "./Picto/Picto"; import { showActionSheetWithOptions } from "./StateHandlers/ActionSheetStateHandler"; import { TableViewPicto } from "./TableView/TableViewImage"; -import { NotificationPermissionStatus } from "../features/notifications/types/Notifications.types"; -import { invalidateProfileSocialsQuery } from "@/queries/useProfileSocialsQuery"; -import { invalidateInboxProfileSocialsQuery } from "@/queries/useInboxProfileSocialsQuery"; type Props = { account: string; @@ -55,7 +51,6 @@ export default function AccountSettingsButton({ account }: Props) { const { setCurrentAccount } = useAccountsStore( useSelect(["setCurrentAccount"]) ); - const erroredAccountsMap = useErroredAccountsMap(); const colorScheme = useColorScheme(); const showDisconnectActionSheet = useDisconnectActionSheet(account); @@ -110,7 +105,7 @@ export default function AccountSettingsButton({ account }: Props) { const options = Object.keys(methods); const icons = []; - if (erroredAccountsMap[account] && isInternetReachable) { + if (isInternetReachable) { icons.push( = ({ style }) => { - const colorScheme = useColorScheme(); - return ( - - - - ); -}; - -const styles = StyleSheet.create({ - picto: { - width: PictoSizes.textButton, - height: PictoSizes.textButton, - marginLeft: 5, - marginTop: -6, - }, - container: { - justifyContent: "center", - }, -}); diff --git a/components/Snackbar/SnackbarBackdrop/SnackbarBackdrop.tsx b/components/Snackbar/SnackbarBackdrop/SnackbarBackdrop.tsx index bf24d50d0..c58e699cc 100644 --- a/components/Snackbar/SnackbarBackdrop/SnackbarBackdrop.tsx +++ b/components/Snackbar/SnackbarBackdrop/SnackbarBackdrop.tsx @@ -1,3 +1,5 @@ +import { useAppTheme } from "@/theme/useAppTheme"; +import { hexToRGBA } from "@/utils/colors"; import { useSnackbars } from "@components/Snackbar/Snackbar.service"; import { useGradientHeight } from "@components/Snackbar/SnackbarBackdrop/SnackbarBackdrop.utils"; // import MaskedView from "@react-native-masked-view/masked-view"; @@ -18,6 +20,7 @@ import Animated, { export const SnackbarBackdrop = memo(() => { const snackbars = useSnackbars(); + const { theme } = useAppTheme(); const { height: windowHeight } = useWindowDimensions(); const gradientHeight = useGradientHeight(); @@ -38,7 +41,10 @@ export const SnackbarBackdrop = memo(() => { > {/* TODO: Maybe add back later. For now masked view can be a litte unstable and buggy */} diff --git a/components/StateHandlers/HydrationStateHandler.tsx b/components/StateHandlers/HydrationStateHandler.tsx index e9f67a58e..bc3728d0a 100644 --- a/components/StateHandlers/HydrationStateHandler.tsx +++ b/components/StateHandlers/HydrationStateHandler.tsx @@ -1,11 +1,10 @@ import { prefetchInboxIdQuery } from "@/queries/use-inbox-id-query"; -import { fetchPersistedConversationListQuery } from "@/queries/useConversationListQuery"; -import logger from "@utils/logger"; -import { useEffect } from "react"; import { getAccountsList } from "@data/store/accountsStore"; import { useAppStore } from "@data/store/appStore"; -import { getXmtpClient } from "@utils/xmtpRN/sync"; +import logger from "@utils/logger"; +import { useEffect } from "react"; import { getInstalledWallets } from "../Onboarding/ConnectViaWallet/ConnectViaWalletSupportedWallets"; +import { prefetchConversationsQuery } from "@/queries/conversations-query"; export default function HydrationStateHandler() { // Initial hydration @@ -24,7 +23,9 @@ export default function HydrationStateHandler() { // Fetching persisted conversation lists for all accounts // We may want to fetch only the selected account's conversation list // in the future, but this is simple for now, and want to get feedback to really confirm - logger.debug("[Hydration] Fetching persisted conversation list"); + logger.debug( + "[Hydration] Fetching persisted conversation list for all accounts" + ); await Promise.allSettled( accounts.map(async (account) => { const accountStartTime = new Date().getTime(); @@ -32,18 +33,8 @@ export default function HydrationStateHandler() { `[Hydration] Fetching persisted conversation list for ${account}` ); - const results = await Promise.allSettled([ - // This will handle creating the client and setting the conversation list from persistence - fetchPersistedConversationListQuery({ account }), - ]); prefetchInboxIdQuery({ account }); - - const errors = results.filter( - (result) => result.status === "rejected" - ); - if (errors.length > 0) { - logger.warn(`[Hydration] error for ${account}:`, errors); - } + prefetchConversationsQuery({ account }); const accountEndTime = new Date().getTime(); logger.debug( diff --git a/components/XmtpEngine.tsx b/components/XmtpEngine.tsx index fd94e00dd..3883671a7 100644 --- a/components/XmtpEngine.tsx +++ b/components/XmtpEngine.tsx @@ -6,14 +6,8 @@ import { AppStateStatus, NativeEventSubscription, } from "react-native"; - -import { - getAccountsList, - getChatStore, - useAccountsStore, -} from "../data/store/accountsStore"; +import { getAccountsList, useAccountsStore } from "../data/store/accountsStore"; import { useAppStore } from "../data/store/appStore"; -import { getTopicsData } from "../utils/api"; import { stopStreamingConversations } from "../utils/xmtpRN/conversations"; import { syncConversationListXmtpClient } from "../utils/xmtpRN/sync"; @@ -131,9 +125,6 @@ class XmtpEngine { accountsToSync.forEach((a) => { if (!this.syncingAccounts[a]) { logger.info(`[XmtpEngine] Syncing account ${a}`); - getTopicsData(a).then((topicsData) => { - getChatStore(a).getState().setTopicsData(topicsData, true); - }); this.syncedAccounts[a] = true; this.syncingAccounts[a] = true; syncConversationListXmtpClient(a) diff --git a/components/swipeable.tsx b/components/swipeable.tsx index cbc6c8549..019360c2e 100644 --- a/components/swipeable.tsx +++ b/components/swipeable.tsx @@ -12,7 +12,6 @@ export type ISwipeableRenderActionsArgs = { progressAnimatedValue: SharedValue; dragAnimatedValue: SharedValue; swipeable: SwipeableMethods; - // caca: number; }; export type ISwipeableProps = { diff --git a/data/helpers/conversations/spamScore.ts b/data/helpers/conversations/spamScore.ts index c409eef43..21d3245cf 100644 --- a/data/helpers/conversations/spamScore.ts +++ b/data/helpers/conversations/spamScore.ts @@ -5,12 +5,12 @@ import { } from "@/utils/xmtpRN/content-types/content-types"; type V3SpameScoreParams = { - message: string; + messageText: string; contentType: IConvosContentType; }; export const getV3SpamScore = async ({ - message, + messageText, contentType, }: V3SpameScoreParams): Promise => { // TODO: Check if adder has been approved already @@ -19,7 +19,7 @@ export const getV3SpamScore = async ({ // TODO: Check spam score between sender and receiver // Check contents of last message - spamScore += computeMessageContentSpamScore(message, contentType); + spamScore += computeMessageContentSpamScore(messageText, contentType); return spamScore; }; diff --git a/data/store/accountsStore.ts b/data/store/accountsStore.ts index c015492ba..0052c5fcf 100644 --- a/data/store/accountsStore.ts +++ b/data/store/accountsStore.ts @@ -3,6 +3,8 @@ import { v4 as uuidv4 } from "uuid"; import { create, StoreApi, UseBoundStore } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; +import { removeLogoutTask } from "@utils/logout"; +import mmkv, { zustandMMKVStorage } from "../../utils/mmkv"; import { ChatStoreType, initChatStore } from "./chatStore"; import { initRecommendationsStore, @@ -18,8 +20,6 @@ import { TransactionsStoreType, } from "./transactionsStore"; import { initWalletStore, WalletStoreType } from "./walletStore"; -import { removeLogoutTask } from "@utils/logout"; -import mmkv, { zustandMMKVStorage } from "../../utils/mmkv"; type AccountStoreType = { [K in keyof AccountStoreDataType]: UseBoundStore< @@ -71,22 +71,6 @@ export const useAccountsList = () => { return accounts.filter((a) => a && a !== TEMPORARY_ACCOUNT_NAME); }; -export const useErroredAccountsMap = () => { - const accounts = useAccountsList(); - return accounts.reduce( - (acc, a) => { - const errored = getChatStore(a).getState().errored; - - if (errored) { - acc[a] = errored; - } - - return acc; - }, - {} as { [account: string]: boolean } - ); -}; - // This store is global (i.e. not linked to an account) // For now we only use a single account so we initialize it // and don't add a setter. diff --git a/data/store/chatStore.ts b/data/store/chatStore.ts index 1d4d8cd6d..67c20ad08 100644 --- a/data/store/chatStore.ts +++ b/data/store/chatStore.ts @@ -1,59 +1,10 @@ -import logger from "@utils/logger"; -import { ConversationTopic } from "@xmtp/react-native-sdk"; -import isDeepEqual from "fast-deep-equal"; +import { zustandMMKVStorage } from "@utils/mmkv"; import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; -import { zustandMMKVStorage } from "../../utils/mmkv"; - -export type TopicStatus = "deleted" | "unread" | "read"; - -export type TopicData = { - status: TopicStatus; - readUntil?: number; - timestamp?: number; - isPinned?: boolean; -}; - -export type TopicsData = { - [topic: string]: TopicData | undefined; -}; export type ChatStoreType = { - openedConversationTopic: string | null; - lastUpdateAt: number; - lastSyncedAt: number; - lastSyncedTopics: string[]; - initialLoadDone: boolean; - initialLoadDoneOnce: boolean; - localClientConnected: boolean; - resyncing: boolean; reconnecting: boolean; - errored: boolean; - topicsData: TopicsData; - topicsDataFetchedOnce: boolean | undefined; - - searchQuery: string; - setSearchQuery: (query: string) => void; - searchBarFocused: boolean; - setSearchBarFocused: (focused: boolean) => void; - - setInitialLoadDone: () => void; - - setLocalClientConnected: (connected: boolean) => void; - setResyncing: (syncing: boolean) => void; setReconnecting: (reconnecting: boolean) => void; - setErrored: (errored: boolean) => void; - setLastSyncedAt: (synced: number, topics: string[]) => void; - - setTopicsData: ( - topicsData: { [topic: string]: TopicData }, - markAsFetchedOnce?: boolean - ) => void; - - // setConversationsLastNotificationSubscribePeriod: ( - // topics: string[], - // period: number - // ) => void; groupInviteLinks: { [topic: string]: string; @@ -62,183 +13,39 @@ export type ChatStoreType = { deleteGroupInviteLink: (topic: string) => void; }; -const now = () => new Date().getTime(); - export const initChatStore = (account: string) => { - const chatStore = create()( + return create()( persist( - (set) => - ({ - lastSyncedAt: 0, - lastSyncedTopics: [], - topicsData: {}, - topicsDataFetchedOnce: false, - openedConversationTopic: "", - lastUpdateAt: 0, - searchQuery: "", - setSearchQuery: (q) => set(() => ({ searchQuery: q })), - searchBarFocused: false, - setSearchBarFocused: (f) => set(() => ({ searchBarFocused: f })), - - initialLoadDone: false, - initialLoadDoneOnce: false, - setInitialLoadDone: () => - set((state) => { - // Called at the end of the initial load. - const newState = { - ...state, - initialLoadDone: true, - initialLoadDoneOnce: true, - lastUpdateAt: now(), - }; - // if (!state.initialLoadDoneOnce) { - // if ( - // state.topicsDataFetchedOnce && - // Object.keys(state.topicsData).length === 0 - // ) { - // const topicsUpdates = getTopicsUpdatesAsRead( - // newState.conversations - // ); - // newState.topicsData = topicsUpdates; - // saveTopicsData(account, topicsUpdates); - // } else { - // setTimeout(() => { - // markConversationsAsReadIfNecessary(account); - // }, 100); - // } - // } - return newState; - }), - localClientConnected: false, - setLocalClientConnected: (c) => - set(() => ({ localClientConnected: c })), - resyncing: false, - setResyncing: (syncing) => set(() => ({ resyncing: syncing })), - reconnecting: false, - setReconnecting: (reconnecting) => set(() => ({ reconnecting })), - errored: false, - setErrored: (errored) => set(() => ({ errored })), - setLastSyncedAt: (synced: number, topics: string[]) => - set(() => ({ lastSyncedAt: synced, lastSyncedTopics: topics })), - setTopicsData: ( - topicsData: { [topic: string]: TopicData }, - markAsFetchedOnce?: boolean - ) => - set((state) => { - const newTopicsData = { - ...state.topicsData, - }; - Object.keys(topicsData).forEach((topic) => { - const oldTopicData = (newTopicsData[topic] || {}) as TopicData; - const oldReadUntil = oldTopicData.readUntil; - const newReadUntil = topicsData[topic].readUntil; - const oldTimestamp = oldTopicData.timestamp; - const newTimestamp = topicsData[topic].timestamp; - if ( - (oldTimestamp && - newTimestamp && - newTimestamp < oldTimestamp) || - (oldReadUntil && newReadUntil && newReadUntil < oldReadUntil) - ) { - // Ignore because it's stale data - } else { - newTopicsData[topic] = { - ...oldTopicData, - ...topicsData[topic], - }; - } - }); - if ( - isDeepEqual(state.topicsData, newTopicsData) && - state.topicsDataFetchedOnce - ) - return state; - return { - topicsData: newTopicsData, - topicsDataFetchedOnce: markAsFetchedOnce - ? true - : state.topicsDataFetchedOnce, - lastUpdateAt: now(), - }; - }), - // setConversationsLastNotificationSubscribePeriod: ( - // topics: string[], - // period: number - // ) => - // set((state) => { - // const newConversations = { ...state.conversations }; - // topics.forEach((topic) => { - // if (newConversations[topic]) { - // newConversations[topic].lastNotificationsSubscribedPeriod = - // period; - // } - // }); - // return { conversations: newConversations }; - // }), - groupInviteLinks: {}, - setGroupInviteLink(topic, inviteLink) { - set((state) => { - const newGroupInvites = { ...state.groupInviteLinks }; - newGroupInvites[topic] = inviteLink; - return { groupInviteLinks: newGroupInvites }; - }); - }, - deleteGroupInviteLink(topic) { - set((state) => { - const newGroupInvites = { ...state.groupInviteLinks }; - delete newGroupInvites[topic]; - return { groupInviteLinks: newGroupInvites }; - }); - }, - }) as ChatStoreType, + (set) => ({ + reconnecting: false, + setReconnecting: (reconnecting) => set(() => ({ reconnecting })), + + groupInviteLinks: {}, + setGroupInviteLink(topic, inviteLink) { + set((state) => { + const newGroupInvites = { ...state.groupInviteLinks }; + newGroupInvites[topic] = inviteLink; + return { groupInviteLinks: newGroupInvites }; + }); + }, + deleteGroupInviteLink(topic) { + set((state) => { + const newGroupInvites = { ...state.groupInviteLinks }; + delete newGroupInvites[topic]; + return { groupInviteLinks: newGroupInvites }; + }); + }, + }), { - name: `store-${account}-chat`, // Account-based storage so each account can have its own chat data + name: `chat-${account}-store`, storage: createJSONStorage(() => zustandMMKVStorage), // Only persisting the information we want partialize: (state) => { - // Persist nothing in web - - const persistedState: Partial = { - initialLoadDoneOnce: state.initialLoadDoneOnce, - lastSyncedAt: state.lastSyncedAt, - lastSyncedTopics: state.lastSyncedTopics, - topicsData: state.topicsData, + return { groupInviteLinks: state.groupInviteLinks, }; - - return persistedState; - }, - version: 3, - migrate: (persistedState: any, version: number): ChatStoreType => { - logger.debug("Zustand migration version:", version); - // Migration from version 0: Convert 'deletedTopics' to 'topicsStatus' - if (version < 1 && persistedState.deletedTopics) { - persistedState.topicsStatus = {}; - for (const [topic, isDeleted] of Object.entries( - persistedState.deletedTopics - )) { - if (isDeleted) { - persistedState.topicsStatus[topic] = "deleted"; - } - } - delete persistedState.deletedTopics; - } - if (version < 2 && persistedState.topicsStatus) { - persistedState.topicsData = {}; - for (const [topic, status] of Object.entries( - persistedState.topicsStatus - )) { - persistedState.topicsData[topic] = { - status, - }; - } - delete persistedState.topicsStatus; - } - - return persistedState as ChatStoreType; }, } ) ); - return chatStore; }; diff --git a/design-system/auto-sized-image.tsx b/design-system/auto-sized-image.tsx new file mode 100644 index 000000000..385e470e7 --- /dev/null +++ b/design-system/auto-sized-image.tsx @@ -0,0 +1,80 @@ +import { IImageProps, Image } from "@/design-system/image"; +import { useLayoutEffect, useState } from "react"; +import { ImageURISource, Platform, Image as RNImage } from "react-native"; + +export type IAutoSizedImageProps = IImageProps & { + /** + * How wide should the image be? + */ + maxWidth?: number; + /** + * How tall should the image be? + */ + maxHeight?: number; +}; + +/** + * An Image component that automatically sizes a remote or data-uri image. + * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/components/AutoImage/} + * @param {IAutoSizedImageProps} props - The props for the `AutoImage` component. + * @returns {JSX.Element} The rendered `AutoImage` component. + */ +export function AutoSizedImage(props: IAutoSizedImageProps) { + const { maxWidth, maxHeight, ...ImageProps } = props; + const source = props.source as ImageURISource; + + const [width, height] = useAutoSizeImage( + Platform.select({ + web: (source?.uri as string) ?? (source as string), + default: source?.uri as string, + }), + [maxWidth, maxHeight] + ); + + return ; +} + +/** + * A hook that will return the scaled dimensions of an image based on the + * provided dimensions' aspect ratio. If no desired dimensions are provided, + * it will return the original dimensions of the remote image. + * + * How is this different from `resizeMode: 'contain'`? Firstly, you can + * specify only one side's size (not both). Secondly, the image will scale to fit + * the desired dimensions instead of just being contained within its image-container. + * @param {number} remoteUri - The URI of the remote image. + * @param {number} dimensions - The desired dimensions of the image. If not provided, the original dimensions will be returned. + * @returns {[number, number]} - The scaled dimensions of the image. + */ +export function useAutoSizeImage( + remoteUri: string, + dimensions?: [maxWidth?: number, maxHeight?: number] +): [width: number, height: number] { + const [[remoteWidth, remoteHeight], setRemoteImageDimensions] = useState([ + 0, 0, + ]); + const remoteAspectRatio = remoteWidth / remoteHeight; + const [maxWidth, maxHeight] = dimensions ?? []; + + useLayoutEffect(() => { + if (!remoteUri) return; + + RNImage.getSize(remoteUri, (w, h) => setRemoteImageDimensions([w, h])); + }, [remoteUri]); + + if (Number.isNaN(remoteAspectRatio)) return [0, 0]; + + if (maxWidth && maxHeight) { + const aspectRatio = Math.min( + maxWidth / remoteWidth, + maxHeight / remoteHeight + ); + return [remoteWidth * aspectRatio, remoteHeight * aspectRatio]; + } else if (maxWidth) { + return [maxWidth, maxWidth / remoteAspectRatio]; + } else if (maxHeight) { + return [maxHeight * remoteAspectRatio, maxHeight]; + } else { + return [remoteWidth, remoteHeight]; + } +} diff --git a/features/GroupInvites/joinGroup/JoinGroup.client.ts b/features/GroupInvites/joinGroup/JoinGroup.client.ts index c51e66f23..69d69a076 100644 --- a/features/GroupInvites/joinGroup/JoinGroup.client.ts +++ b/features/GroupInvites/joinGroup/JoinGroup.client.ts @@ -21,7 +21,7 @@ import { AxiosInstance } from "axios"; import {} from "../groupInvites.utils"; import { JoinGroupResult } from "./joinGroup.types"; -import { ConversationListQueryData } from "@/queries/useConversationListQuery"; +import { IConversationsQuery } from "@/queries/conversations-query"; import { entify } from "@/queries/entify"; import { wait } from "@/utils/general"; @@ -90,12 +90,12 @@ export class JoinGroupClient { const liveFetchGroupsByAccount = async ( account: string ): Promise => { - const { fetchConversationListQuery } = await import( - "@/queries/useConversationListQuery" + const { fetchConversationsQuery } = await import( + "@/queries/conversations-query" ); - const conversationList: ConversationListQueryData = - await fetchConversationListQuery({ account }); + const conversationList: IConversationsQuery = + await fetchConversationsQuery({ account }); const conversationEntity: ConversationDataEntity = entify( conversationList, diff --git a/features/blocked-chats/blocked-chats.tsx b/features/blocked-chats/blocked-chats.tsx index da0ad7b73..6e02b3eb5 100644 --- a/features/blocked-chats/blocked-chats.tsx +++ b/features/blocked-chats/blocked-chats.tsx @@ -1,15 +1,15 @@ import { VStack } from "@/design-system/VStack"; import { EmptyState } from "@/design-system/empty-state"; -import { ConversationList } from "@/features/conversation-list/components/conversation-list/conversation-list"; +import { useBlockedChatsForCurrentAccount } from "@/features/blocked-chats/use-blocked-chats"; +import { ConversationList } from "@/features/conversation-list/conversation-list"; import { useHeader } from "@/navigation/use-header"; import { useRouter } from "@/navigation/useNavigation"; import { $globalStyles } from "@/theme/styles"; import { translate } from "@i18n/index"; import React from "react"; -import { useAllBlockedChats } from "./useAllBlockedChats"; export function BlockedChatsScreen() { - const { data: allBlockedChats = [] } = useAllBlockedChats(); + const { data: allBlockedChats = [] } = useBlockedChatsForCurrentAccount(); const router = useRouter(); diff --git a/features/blocked-chats/use-blocked-chats.ts b/features/blocked-chats/use-blocked-chats.ts new file mode 100644 index 000000000..796feea05 --- /dev/null +++ b/features/blocked-chats/use-blocked-chats.ts @@ -0,0 +1,40 @@ +import { getConversationDataQueryOptions } from "@/queries/conversation-data-query"; +import { useConversationsQuery } from "@/queries/conversations-query"; +import { useCurrentAccount } from "@data/store/accountsStore"; +import { useQueries } from "@tanstack/react-query"; +import { useMemo } from "react"; + +export const useBlockedChatsForCurrentAccount = () => { + const currentAccount = useCurrentAccount(); + + const { data } = useConversationsQuery({ + account: currentAccount!, + context: "useBlockedChats", + }); + + const conversationsDataQueries = useQueries({ + queries: (data ?? []).map((conversation) => + getConversationDataQueryOptions({ + account: currentAccount!, + topic: conversation.topic, + context: "useBlockedChats", + }) + ), + }); + + const blockedConversations = useMemo(() => { + if (!data) return []; + + return data.filter((conversation, index) => { + const query = conversationsDataQueries[index]; + return ( + // Include deleted conversations + query?.data?.isDeleted || + // Include denied conversations + conversation.state === "denied" + ); + }); + }, [data, conversationsDataQueries]); + + return { data: blockedConversations }; +}; diff --git a/features/blocked-chats/useAllBlockedChats.ts b/features/blocked-chats/useAllBlockedChats.ts deleted file mode 100644 index 8595151d7..000000000 --- a/features/blocked-chats/useAllBlockedChats.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useV3BlockedChats } from "./useV3BlockedChats"; - -export const useAllBlockedChats = () => { - return useV3BlockedChats(); -}; diff --git a/features/blocked-chats/useV3BlockedChats.ts b/features/blocked-chats/useV3BlockedChats.ts deleted file mode 100644 index 46591a6e3..000000000 --- a/features/blocked-chats/useV3BlockedChats.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useCurrentAccount } from "@data/store/accountsStore"; -import { useTopicsData } from "@hooks/useTopicsData"; -import { useConversationListQuery } from "@/queries/useConversationListQuery"; -import { useMemo } from "react"; - -export const useV3BlockedChats = () => { - const currentAccount = useCurrentAccount(); - const topicsData = useTopicsData(); - const { data, ...rest } = useConversationListQuery({ - account: currentAccount!, - queryOptions: { - refetchOnMount: false, - }, - context: "useV3BlockedChats", - }); - - const blockedConversations = useMemo(() => { - return data?.filter((convo) => { - const topicData = topicsData[convo.topic]; - return topicData?.status === "deleted" || convo.state === "denied"; - }); - }, [data, topicsData]); - - return { data: blockedConversations, ...rest }; -}; diff --git a/features/conversation-list/ConversationListNav.ios.deprecated.tsx b/features/conversation-list/ConversationListNav.ios.deprecated.tsx deleted file mode 100644 index 4dc15a8da..000000000 --- a/features/conversation-list/ConversationListNav.ios.deprecated.tsx +++ /dev/null @@ -1,227 +0,0 @@ -/** - * KEEP UNTIL WE FINISHED REFACTORING THE CONVERSATION LIST - */ -// import { ErroredHeader } from "@components/ErroredHeader"; -// import { useShouldShowErrored } from "@hooks/useShouldShowErrored"; -// import { NativeStackScreenProps } from "@react-navigation/native-stack"; -// import { textPrimaryColor, textSecondaryColor } from "@styles/colors"; -// import React, { useLayoutEffect } from "react"; -// import { -// NativeSyntheticEvent, -// StyleSheet, -// Text, -// TextInputChangeEventData, -// TouchableOpacity, -// View, -// useColorScheme, -// } from "react-native"; -// import { SearchBarCommands } from "react-native-screens"; - -// import Button from "../../components/Button/Button"; -// import { -// useShouldShowConnecting, -// useShouldShowConnectingOrSyncing, -// } from "../../components/Connecting"; -// import NewConversationButton from "../../components/ConversationList/NewConversationButton"; -// import ProfileSettingsButton from "../../components/ConversationList/ProfileSettingsButton"; -// import { useAccountsStore, useChatStore } from "../../data/store/accountsStore"; -// import { useSelect } from "../../data/store/storeHelpers"; -// import { navigate } from "../../utils/navigation"; -// import { shortDisplayName } from "../../utils/str"; -// import ConversationList from "../ConversationList"; -// import { -// NativeStack, -// NavigationParamList, -// navigationAnimation, -// } from "./Navigation"; -// import { usePreferredName } from "@/hooks/usePreferredName"; -// import { useProfileSocialsQuery } from "@/queries/useProfileSocialsQuery"; -// import { Loader } from "@/design-system/loader"; -// import { getReadableProfile } from "@/utils/getReadableProfile"; - -// type HeaderSearchBarProps = { -// searchBarRef: React.RefObject; -// autoHide: boolean; -// showSearchBar: boolean; -// } & NativeStackScreenProps; - -// // If we set the search bar in the NativeStack.Screen and navigate to it, -// // it show before hiding so we do an exception and don't set it in the Screen -// // but using useLayoutEffect. To avoid warning and search not always being set, -// // useLayoutEffect must NOT be conditional so we can't hide it with conditions… - -// // export const useHeaderSearchBar = ({ -// // navigation, -// // searchBarRef, -// // autoHide, -// // showSearchBar = true, -// // }: HeaderSearchBarProps) => { -// // const { setSearchQuery, setSearchBarFocused } = useChatStore( -// // useSelect(["setSearchQuery", "setSearchBarFocused"]) -// // ); - -// // useLayoutEffect(() => { -// // if (!showSearchBar) { -// // navigation.setOptions({ -// // headerSearchBarOptions: undefined, -// // }); -// // return; -// // } -// // navigation.setOptions({ -// // headerSearchBarOptions: { -// // ref: searchBarRef as React.RefObject, -// // hideNavigationBar: autoHide, -// // hideWhenScrolling: autoHide, -// // autoFocus: false, -// // placeholder: "Search", -// // onChangeText: ( -// // event: NativeSyntheticEvent -// // ) => { -// // setSearchQuery(event.nativeEvent.text); -// // }, -// // onFocus: () => setSearchBarFocused(true), -// // onCancelButtonPress: () => setSearchBarFocused(false), -// // }, -// // }); -// // }, [ -// // autoHide, -// // navigation, -// // searchBarRef, -// // setSearchBarFocused, -// // setSearchQuery, -// // showSearchBar, -// // ]); -// // }; - -// export default function ConversationListNav() { -// const colorScheme = useColorScheme(); - -// const searchBarRef = React.useRef( -// null -// ) as React.MutableRefObject; - -// const shouldShowConnectingOrSyncing = useShouldShowConnectingOrSyncing(); -// const shouldShowConnecting = useShouldShowConnecting(); -// const shouldShowError = useShouldShowErrored(); -// const currentAccount = useAccountsStore((s) => s.currentAccount); - -// const { isLoading } = useProfileSocialsQuery(currentAccount, currentAccount); - -// const preferredName = usePreferredName(currentAccount); - -// // Delays a little flash of the name when loading, as default is a long ugly address -// const name = isLoading ? "" : preferredName; - -// return ( -// ({ -// // headerTitle: () => -// // shouldShowConnectingOrSyncing ? ( -// // -// // {shouldShowConnectingOrSyncing && } -// // {shouldShowConnecting.warnMessage && ( -// // -// // {shouldShowConnecting.warnMessage} -// // -// // )} -// // -// // ) : ( -// // -// // ), -// // headerBackTitle: getReadableProfile(currentAccount, currentAccount), -// // headerRight: () => ( -// // -// //