Skip to content

Commit 0774e57

Browse files
authored
feat: Persistence + Our own types for conversations, messages, groups, etc (#1639)
1 parent cbb308e commit 0774e57

File tree

168 files changed

+2073
-1306
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+2073
-1306
lines changed

App.tsx

+21-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { useReactQueryDevTools } from "@dev-plugins/react-query"
33
import { ActionSheetProvider } from "@expo/react-native-action-sheet"
44
import { Chain, PrivyProvider } from "@privy-io/expo"
55
import { SmartWalletsProvider } from "@privy-io/expo/smart-wallets"
6-
import { QueryClientProvider } from "@tanstack/react-query"
6+
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
7+
import { DevToolsBubble } from "react-native-react-query-devtools"
78
import { ActionSheet } from "@/components/action-sheet"
89
import { DebugProvider } from "@/components/debug-provider"
910
import { Snackbars } from "@/components/snackbar/snackbars"
@@ -13,17 +14,19 @@ import { $globalStyles } from "@/theme/styles"
1314
import { useThemeProvider } from "@/theme/use-app-theme"
1415
import { useCachedResources } from "@/utils/cache-resources"
1516
import { reactQueryClient } from "@/utils/react-query/react-query.client"
17+
import { DEFAULT_GC_TIME } from "@/utils/react-query/react-query.constants"
18+
import { reactQueryPersister } from "@/utils/react-query/react-query.utils"
1619
import "expo-dev-client"
1720
import React, { useEffect } from "react"
1821
import { GestureHandlerRootView } from "react-native-gesture-handler"
1922
import { KeyboardProvider } from "react-native-keyboard-controller"
23+
import { SafeAreaProvider } from "react-native-safe-area-context"
2024
import { ThirdwebProvider } from "thirdweb/react"
2125
import { base } from "viem/chains"
2226
import { config } from "./config"
2327
import { useMonitorNetworkConnectivity } from "./dependencies/NetworkMonitor/use-monitor-network-connectivity"
2428
import { AppNavigator } from "./navigation/app-navigator"
2529
import "./utils/ignore-logs"
26-
import { SafeAreaProvider } from "react-native-safe-area-context"
2730
import { sentryInit } from "./utils/sentry"
2831
import { preventSplashScreenAutoHide } from "./utils/splash/splash"
2932

@@ -58,14 +61,19 @@ export function App() {
5861
}, [])
5962

6063
return (
61-
// <PersistQueryClientProvider
62-
// client={queryClient}
63-
// persistOptions={{
64-
// persister: reactQueryPersister,
65-
// maxAge: DEFAULT_GC_TIME,
66-
// }}
67-
// >
68-
<QueryClientProvider client={reactQueryClient}>
64+
<PersistQueryClientProvider
65+
client={reactQueryClient}
66+
persistOptions={{
67+
persister: reactQueryPersister,
68+
maxAge: DEFAULT_GC_TIME,
69+
dehydrateOptions: {
70+
shouldDehydrateQuery(query) {
71+
return query.meta?.persist !== false
72+
},
73+
},
74+
}}
75+
>
76+
{/* <QueryClientProvider client={reactQueryClient}> */}
6977
<PrivyProvider
7078
appId={config.privy.appId}
7179
clientId={config.privy.clientId}
@@ -83,7 +91,7 @@ export function App() {
8391
{/* <AuthenticateWithPasskeyProvider> */}
8492
<AppNavigator />
8593
{/* </AuthenticateWithPasskeyProvider> */}
86-
{/* {__DEV__ && <DevToolsBubble />} */}
94+
{__DEV__ && <DevToolsBubble />}
8795
<Snackbars />
8896
<ActionSheet />
8997
</BottomSheetModalProvider>
@@ -96,7 +104,7 @@ export function App() {
96104
</ThirdwebProvider>
97105
</SmartWalletsProvider>
98106
</PrivyProvider>
99-
</QueryClientProvider>
100-
// </PersistQueryClientProvider>
107+
{/* </QueryClientProvider> */}
108+
</PersistQueryClientProvider>
101109
)
102110
}

components/debug-provider.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { $globalStyles } from "@/theme/styles"
2323
import { captureError } from "@/utils/capture-error"
2424
import { GenericError } from "@/utils/error"
2525
import { getEnv } from "@/utils/getEnv"
26+
import { ObjectTyped } from "@/utils/object-typed"
2627
import { showActionSheet } from "./action-sheet"
2728

2829
export function DebugProvider(props: { children: React.ReactNode }) {
@@ -258,17 +259,17 @@ function useShowDebugMenu() {
258259
},
259260
"Show Streaming Status": () => {
260261
const { accountStreamingStates } = useStreamingStore.getState()
261-
const accounts = Object.keys(accountStreamingStates)
262+
const accounts = ObjectTyped.keys(accountStreamingStates)
262263

263264
if (accounts.length === 0) {
264265
Alert.alert("No Streaming States", "No accounts are currently streaming")
265266
return
266267
}
267268

268-
const statusMessages = accounts.map((account) => {
269-
const state = accountStreamingStates[account]
269+
const statusMessages = accounts.map((inboxId) => {
270+
const state = accountStreamingStates[inboxId]
270271
return [
271-
`Account: ${account}`,
272+
`InboxId: ${inboxId}`,
272273
`Conversations Streaming: ${state.isStreamingConversations ? "ON" : "OFF"}`,
273274
`Messages Streaming: ${state.isStreamingMessages ? "ON" : "OFF"}`,
274275
`Consent Streaming: ${state.isStreamingConsent ? "ON" : "OFF"}`,

components/group-avatar.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { IXmtpConversationTopic, IXmtpInboxId } from "@features/xmtp/xmtp.types"
1+
import { IXmtpInboxId } from "@features/xmtp/xmtp.types"
22
import React, { memo, useMemo } from "react"
33
import { StyleProp, TextStyle, ViewStyle } from "react-native"
44
import { Center } from "@/design-system/Center"
55
import { Text } from "@/design-system/Text"
66
import { VStack } from "@/design-system/VStack"
77
import { useSafeCurrentSender } from "@/features/authentication/multi-inbox.store"
8-
import { useGroupMembersQuery } from "@/features/groups/useGroupMembersQuery"
9-
import { useGroupQuery } from "@/features/groups/useGroupQuery"
8+
import { IConversationTopic } from "@/features/conversation/conversation.types"
9+
import { useGroupMembersQuery } from "@/features/groups/group-members.query"
10+
import { useGroupQuery } from "@/features/groups/group.query"
1011
import { usePreferredDisplayInfoBatch } from "@/features/preferred-display-info/use-preferred-display-info-batch"
1112
import { $globalStyles } from "@/theme/styles"
1213
import { ThemedStyle, useAppTheme } from "@/theme/use-app-theme"
@@ -81,7 +82,7 @@ export const GroupAvatarInboxIds = memo(function GroupAvatarInboxIds(props: {
8182
* Will render the group image if available, otherwise shows member avatars
8283
*/
8384
export const GroupAvatar = memo(function GroupAvatar(props: {
84-
groupTopic: IXmtpConversationTopic
85+
groupTopic: IConversationTopic
8586
size?: IGroupAvatarSize
8687
sizeNumber?: number
8788
}) {
@@ -169,8 +170,8 @@ export const GroupAvatar = memo(function GroupAvatar(props: {
169170
}, [size, theme, sizeNumberProp])
170171

171172
// If group has an image, use it instead of member avatars
172-
if (group?.groupImageUrl) {
173-
return <Avatar uri={group.groupImageUrl} sizeNumber={sizeNumber} name={group.groupName} />
173+
if (group?.imageUrl) {
174+
return <Avatar uri={group.imageUrl} sizeNumber={sizeNumber} name={group.name} />
174175
}
175176

176177
return <GroupAvatarUI members={memberData} size={sizeNumber} />

design-system/empty-state.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { memo } from "react"
77
import { View } from "react-native"
8+
import { useHeaderHeight } from "@/design-system/Header/Header.utils"
89
import { Icon } from "@/design-system/Icon/Icon"
910
import { IIconName } from "@/design-system/Icon/Icon.types"
1011
import { Text } from "@/design-system/Text"
@@ -18,6 +19,7 @@ type IEmptyStateProps = {
1819
icon?: React.ReactNode
1920
containerStyle?: IVStackProps["style"]
2021
style?: IVStackProps["style"]
22+
hasScreenHeader?: boolean // Adds bottom padding so it can be centered when using it inside a screen that has a header
2123
}
2224

2325
export const EmptyState = memo(function EmptyState({
@@ -27,17 +29,21 @@ export const EmptyState = memo(function EmptyState({
2729
icon,
2830
containerStyle,
2931
style,
32+
hasScreenHeader = false,
3033
}: IEmptyStateProps) {
3134
const { theme } = useAppTheme()
3235

36+
const headerHeight = useHeaderHeight()
37+
3338
return (
3439
<VStack
3540
style={[
3641
{
3742
flex: 1,
3843
alignItems: "center",
3944
justifyContent: "center",
40-
padding: theme.spacing.lg,
45+
paddingHorizontal: theme.spacing.lg,
46+
paddingBottom: hasScreenHeader ? headerHeight : 0,
4147
},
4248
containerStyle,
4349
style,

features/GroupInvites/joinGroup/components/JoinGroupHeader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// * and description (if available).
1010
// *
1111
// * @param {Object} props - The component props
12-
// * @param {string} props.groupName - The name of the group
12+
// * @param {string} props.name - The name of the group
1313
// * @param {string} props.imageUrl - The URL of the group's avatar
1414
// * @param {string} [props.description] - The group's description
1515
// *

features/authentication/components/account-switcher.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@/features/authentication/multi-inbox.store"
1313
import { usePreferredDisplayInfo } from "@/features/preferred-display-info/use-preferred-display-info"
1414
import { usePreferredDisplayInfoBatch } from "@/features/preferred-display-info/use-preferred-display-info-batch"
15+
import { IXmtpInboxId } from "@/features/xmtp/xmtp.types"
1516
import { translate } from "@/i18n"
1617
import { useAppTheme } from "@/theme/use-app-theme"
1718
import { shortAddress } from "@/utils/strings/shortAddress"
@@ -48,7 +49,7 @@ export function AccountSwitcher(props: { noAvatar?: boolean }) {
4849
navigation.navigate("AppSettings")
4950
} else {
5051
useMultiInboxStore.getState().actions.setCurrentSender({
51-
inboxId: profileXmtpInboxId,
52+
inboxId: profileXmtpInboxId as IXmtpInboxId,
5253
})
5354
}
5455
},

features/authentication/use-logout.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { useAuthenticationStore } from "@/features/authentication/authentication
44
import { resetMultiInboxStore } from "@/features/authentication/multi-inbox.store"
55
import { captureError } from "@/utils/capture-error"
66
import { GenericError } from "@/utils/error"
7-
import { reactQueryMMKV, secureQueryMMKV } from "@/utils/mmkv"
87
import { reactQueryClient } from "@/utils/react-query/react-query.client"
8+
import { reactQueryMMKV, secureQueryMMKV } from "@/utils/react-query/react-query.utils"
99
import { logger } from "../../utils/logger"
1010

1111
export const useLogout = () => {

features/consent/consent.types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
import { IXmtpConsentState } from "@/features/xmtp/xmtp.types"
1+
import { ConsentState } from "@xmtp/react-native-sdk"
22

3-
export type IConsentState = IXmtpConsentState
3+
// For now we do 1-1
4+
export type IConsentState = ConsentState

features/consent/consent.utils.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { IConsentState } from "@/features/consent/consent.types"
2+
import { IXmtpConsentState } from "@/features/xmtp/xmtp.types"
3+
4+
export function convertConsentStateToXmtpConsentState(
5+
consentState: IConsentState,
6+
): IXmtpConsentState {
7+
if (consentState === "allowed") {
8+
return "allowed"
9+
}
10+
11+
if (consentState === "unknown") {
12+
return "unknown"
13+
}
14+
15+
return "denied"
16+
}

features/consent/use-allow-dm.mutation.ts

+19-24
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,12 @@ import {
1010
} from "@/features/conversation/conversation-requests-list/conversations-unknown-consent.query"
1111
import { getConversationQueryData } from "@/features/conversation/queries/conversation.query"
1212
import { getDmQueryData, setDmQueryData } from "@/features/dm/use-dm-query"
13-
import {
14-
IXmtpConversationId,
15-
IXmtpConversationTopic,
16-
IXmtpConversationWithCodecs,
17-
IXmtpDmWithCodecs,
18-
IXmtpInboxId,
19-
} from "@/features/xmtp/xmtp.types"
13+
import { IXmtpConversationId, IXmtpInboxId } from "@/features/xmtp/xmtp.types"
2014
import { updateObjectAndMethods } from "@/utils/update-object-and-methods"
15+
import { IConversationId, IConversationTopic } from "../conversation/conversation.types"
2116
import {
2217
setXmtpConsentStateForInboxId,
23-
updateConsentForGroupsForAccount,
18+
updateConsentForGroupsForInbox,
2419
} from "../xmtp/xmtp-consent/xmtp-consent"
2520

2621
export function useAllowDmMutation() {
@@ -29,17 +24,17 @@ export function useAllowDmMutation() {
2924
return useMutation({
3025
mutationFn: async (args: {
3126
peerInboxId: IXmtpInboxId
32-
conversationId: IXmtpConversationId
33-
topic: IXmtpConversationTopic
27+
conversationId: IConversationId
28+
topic: IConversationTopic
3429
}) => {
3530
const { peerInboxId, conversationId } = args
3631
if (!peerInboxId) {
3732
throw new Error("Peer inbox id not found")
3833
}
3934
await Promise.all([
40-
updateConsentForGroupsForAccount({
35+
updateConsentForGroupsForInbox({
4136
clientInboxId: currentSenderInboxId,
42-
groupIds: [conversationId],
37+
groupIds: [conversationId as unknown as IXmtpConversationId],
4338
consent: "allowed",
4439
}),
4540
setXmtpConsentStateForInboxId({
@@ -55,19 +50,19 @@ export function useAllowDmMutation() {
5550
})
5651
if (conversation) {
5752
const updatedDm = updateObjectAndMethods(conversation, {
58-
state: "allowed",
53+
consentState: "allowed",
5954
})
6055

6156
setDmQueryData({
62-
ethAccountAddress: currentSenderInboxId,
63-
inboxId: peerInboxId,
64-
dm: updatedDm as IXmtpDmWithCodecs,
57+
targetInboxId: currentSenderInboxId,
58+
clientInboxId: peerInboxId,
59+
dm: updatedDm,
6560
})
6661

6762
// Add to main conversations list
6863
addConversationToAllowedConsentConversationsQuery({
6964
inboxId: currentSenderInboxId,
70-
conversation: updatedDm as IXmtpConversationWithCodecs,
65+
conversation: updatedDm,
7166
})
7267

7368
// Remove from requests
@@ -76,35 +71,35 @@ export function useAllowDmMutation() {
7671
topic,
7772
})
7873

79-
return { previousDmConsent: conversation.state }
74+
return { previousDmConsent: conversation.consentState }
8075
}
8176
},
8277
onError: (error, { topic, peerInboxId }, context) => {
8378
const { previousDmConsent } = context || {}
8479
if (previousDmConsent) {
8580
const dm = getDmQueryData({
86-
ethAccountAddress: currentSenderInboxId,
87-
inboxId: peerInboxId,
81+
targetInboxId: currentSenderInboxId,
82+
clientInboxId: peerInboxId,
8883
})
8984

9085
if (!dm) {
9186
return
9287
}
9388

9489
const previousDm = updateObjectAndMethods(dm, {
95-
state: previousDmConsent,
90+
consentState: previousDmConsent,
9691
})
9792

9893
setDmQueryData({
99-
ethAccountAddress: currentSenderInboxId,
100-
inboxId: peerInboxId,
94+
targetInboxId: currentSenderInboxId,
95+
clientInboxId: peerInboxId,
10196
dm: previousDm,
10297
})
10398

10499
// Add back in requests
105100
addConversationToUnknownConsentConversationsQuery({
106101
inboxId: currentSenderInboxId,
107-
conversation: previousDm as IXmtpConversationWithCodecs,
102+
conversation: previousDm,
108103
})
109104

110105
// Remove from main conversations list

0 commit comments

Comments
 (0)