Skip to content

Commit 0787148

Browse files
feat: New conversation composer + refactor and fix profile socials + many other chat related fixes (#1569)
Co-authored-by: Thierry <[email protected]>
1 parent cd9f922 commit 0787148

File tree

201 files changed

+5306
-4139
lines changed

Some content is hidden

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

201 files changed

+5306
-4139
lines changed

.cursorrules

+47
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,53 @@
9191
- Keep features self-contained. Create a new feature folder if accessing features across boundaries.
9292
- Place queries within their respective feature folders, but maintain queryKeys at the root level for better visibility.
9393

94+
## File Naming Convention
95+
96+
Use descriptive suffixes:
97+
98+
```
99+
[featureName].screen.tsx
100+
[featureName].nav.tsx
101+
[featureName].store.ts
102+
[featureName].queries.ts
103+
[featureName].utils.ts
104+
[featureName].client.ts
105+
[featureName].types.ts
106+
[featureName].test.ts
107+
```
108+
109+
// ❌ Bad: Generic or unclear names
110+
profile.ts
111+
group-chat.tsx
112+
utils.ts
113+
types.ts
114+
115+
// ✅ Good: Clear, descriptive names with proper suffixes
116+
profile.screen.tsx
117+
group-chat.nav.tsx
118+
conversation.store.ts
119+
messages.queries.ts
120+
profile.utils.ts
121+
auth.client.ts
122+
user.types.ts
123+
login.test.ts
124+
125+
## Imports
126+
127+
Always import using the @alias as examplified below:
128+
129+
// ✅ Good: Using the @ to simplify imports
130+
import { isConversationGroup } from "@/features/conversation/utils/is-conversation-group";
131+
import { isConversationDm } from "@/features/conversation/utils/is-conversation-dm";
132+
import { getReadableProfile } from "@/utils/getReadableProfile";
133+
import { IConversationMembershipSearchResult } from "@/features/search/search.types";
134+
135+
// ❌ Bad: Using relative paths
136+
import { isConversationGroup } from "../../../features/conversation/utils/is-conversation-group";
137+
import { isConversationDm } from "../../../features/conversation/utils/is-conversation-dm";
138+
import { getReadableProfile } from "../../../../utils/getReadableProfile";
139+
import { IConversationMembershipSearchResult } from "../../../features/search/search.types";
140+
94141
## Theming & Styling
95142

96143
- Always use the theme for styling. Import it from @/theme/useAppTheme like `const { theme } = useAppTheme();`.

App.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,19 @@ LogBox.ignoreLogs([
6262
"Error destroying session", // Privy
6363
'event="noNetwork', // ethers
6464
"[Reanimated] Reading from `value` during component render. Please ensure that you do not access the `value` property or use `get` method of a shared value while React is rendering a component.",
65-
"Attempted to import the module",
65+
"Attempted to import the module", // General module import warnings
66+
'Attempted to import the module "/Users', // More specific module import warnings
6667
"Falling back to file-based resolution. Consider updating the call site or asking the package maintainer(s) to expose this API",
6768
"Couldn't find real values for `KeyboardContext`. Please make sure you're inside of `KeyboardProvider` - otherwise functionality of `react-native-keyboard-controller` will not work. [Component Stack]",
6869
"sync worker error storage error: Pool needs to reconnect before use",
6970
"[Converse.debug.dylib] sync worker error storage error: Pool needs to reconnect before use",
7071
"Falling back to file-based resolution. Consider updating the call site or asking the package maintainer(s) to expose this API.",
7172
]);
7273

74+
if (__DEV__) {
75+
require("./ReactotronConfig.ts");
76+
}
77+
7378
// This is the default configuration
7479
configureReanimatedLogger({
7580
level: ReanimatedLogLevel.warn,

ReactotronConfig.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Reactotron from "reactotron-react-native";
2+
import {
3+
QueryClientManager,
4+
reactotronReactQuery,
5+
} from "reactotron-react-query";
6+
import { queryClient } from "./queries/queryClient";
7+
const queryClientManager = new QueryClientManager({
8+
queryClient,
9+
});
10+
11+
// eslint-disable-next-line react-hooks/rules-of-hooks
12+
Reactotron.use(reactotronReactQuery(queryClientManager))
13+
.configure({
14+
onDisconnect: () => {
15+
queryClientManager.unsubscribe();
16+
},
17+
})
18+
.useReactNative()
19+
.connect();

components/Avatar.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export const Avatar = memo(function Avatar({
6868
{name ? (
6969
<Text
7070
weight="medium"
71-
color="primary"
7271
style={{
7372
color: theme.colors.global.white, // white looks better for both dark/light themes
7473
fontSize: avatarSize / 2.4, // 2.4 is the ratio in the Figma design

components/ClickableText.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function ClickableText({ children, style }: Props) {
3838
}, []);
3939

4040
const handleNewConversationPress = useCallback((peer: string) => {
41-
navigate("Conversation", { peer });
41+
// TODO
4242
}, []);
4343

4444
const showCopyActionSheet = useCallback(

components/DebugButton.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { translate } from "@/i18n";
2+
import { getNativeLogFile } from "@/utils/xmtpRN/logs";
13
import * as Sentry from "@sentry/react-native";
24
import {
35
getPreviousSessionLoggingFile,
@@ -6,17 +8,16 @@ import {
68
} from "@utils/logger";
79
import { navigate } from "@utils/navigation";
810
import Share from "@utils/share";
9-
import { getNativeLogFile } from "@/utils/xmtpRN/logs";
1011
import Constants from "expo-constants";
1112
import { Image } from "expo-image";
1213
import * as Updates from "expo-updates";
1314
import { forwardRef, useImperativeHandle } from "react";
1415
import { Alert, Platform } from "react-native";
15-
import { translate } from "@/i18n";
1616
import { config } from "../config";
17-
import { useAccountsList } from "../data/store/accountsStore";
17+
import { useAccountsList, getAccountsList } from "../data/store/accountsStore";
1818
import mmkv from "../utils/mmkv";
1919
import { showActionSheetWithOptions } from "./StateHandlers/ActionSheetStateHandler";
20+
import { logoutAccount } from "@/utils/logout";
2021

2122
export const useDebugEnabled = (address?: string) => {
2223
const accounts = useAccountsList();
@@ -57,6 +58,16 @@ const DebugButton = forwardRef((props, ref) => {
5758
useImperativeHandle(ref, () => ({
5859
showDebugMenu() {
5960
const debugMethods = {
61+
"Logout all accounts": async () => {
62+
const allAccounts = getAccountsList();
63+
try {
64+
for (const account of allAccounts) {
65+
await logoutAccount({ account });
66+
}
67+
} catch (error) {
68+
alert(error);
69+
}
70+
},
6071
"Trigger OTA Update": async () => {
6172
try {
6273
const update = await Updates.fetchUpdateAsync();

components/ParsedText/ParsedText.utils.ts

+16-21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
IDefaultParseShape,
44
IParseShape,
55
} from "@components/ParsedText/ParsedText.types";
6-
import { ObjectTyped } from "@utils/objectTyped";
6+
import { ObjectTyped } from "@/utils/object-typed";
77

88
type ITextPart = {
99
children: string;
@@ -73,27 +73,22 @@ function createMatchedPart(args: {
7373
const { pattern, match, index } = args;
7474
const text = match[0];
7575

76-
const props = ObjectTyped.entries(pattern).reduce(
77-
(acc, [key, value]) => {
78-
if (
79-
["pattern", "renderText", "nonExhaustiveMaxMatchCount"].includes(key)
80-
) {
81-
return acc;
82-
}
83-
84-
if (typeof value === "function") {
85-
// Support onPress / onLongPress functions
86-
acc[key] = () =>
87-
// @ts-ignore
88-
value(text, index);
89-
} else {
90-
// Set a prop with an arbitrary name to the value in the match-config
91-
acc[key] = value;
92-
}
76+
const props = ObjectTyped.entries(pattern).reduce((acc, [key, value]) => {
77+
if (["pattern", "renderText", "nonExhaustiveMaxMatchCount"].includes(key)) {
9378
return acc;
94-
},
95-
{} as Record<string, any>
96-
);
79+
}
80+
81+
if (typeof value === "function") {
82+
// Support onPress / onLongPress functions
83+
acc[key] = () =>
84+
// @ts-ignore
85+
value(text, index);
86+
} else {
87+
// Set a prop with an arbitrary name to the value in the match-config
88+
acc[key] = value;
89+
}
90+
return acc;
91+
}, {} as Record<string, any>);
9792

9893
return {
9994
...props,
File renamed without changes.

components/StateHandlers/HydrationStateHandler.tsx

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
1-
import { fetchConversationsQuery } from "@/queries/use-conversations-query";
2-
import { prefetchInboxIdQuery } from "@/queries/inbox-id-query";
1+
import { subscribeToNotifications } from "@/features/notifications/utils/subscribeToNotifications";
2+
import { prefetchConversationMetadataQuery } from "@/queries/conversation-metadata-query";
3+
import { fetchAllowedConsentConversationsQuery } from "@/queries/conversations-allowed-consent-query";
4+
import { ensureInboxId } from "@/queries/inbox-id-query";
35
import { captureError } from "@/utils/capture-error";
4-
import { getAccountsList } from "@data/store/accountsStore";
6+
import { getAccountsList, useAccountsStore } from "@data/store/accountsStore";
57
import { useAppStore } from "@data/store/appStore";
68
import logger from "@utils/logger";
7-
import { useEffect } from "react";
8-
import { subscribeToNotifications } from "@/features/notifications/utils/subscribeToNotifications";
9-
import { syncConsent } from "@/utils/xmtpRN/xmtp-preferences/xmtp-preferences";
10-
import { prefetchConversationMetadataQuery } from "@/queries/conversation-metadata-query";
119
import { Conversation } from "@xmtp/react-native-sdk";
10+
import { useEffect } from "react";
1211

1312
export default function HydrationStateHandler() {
1413
useEffect(() => {
1514
const hydrate = async () => {
1615
const startTime = new Date().getTime();
1716
const accounts = getAccountsList();
1817

18+
// Critical queries
19+
const results = await Promise.allSettled(
20+
// We need the inboxId for each account since we use them so much
21+
accounts.map((account) => ensureInboxId({ account }))
22+
);
23+
24+
// Remove accounts that failed to get an inboxId
25+
results.forEach((result, index) => {
26+
if (result.status === "rejected") {
27+
const failedAccount = accounts[index];
28+
captureError({
29+
...result.reason,
30+
message: `[Hydration] Failed to get inboxId for account ${failedAccount}, removing account. ${result.reason.message}`,
31+
});
32+
useAccountsStore.getState().removeAccount(failedAccount);
33+
}
34+
});
35+
36+
// Non critical queries
1937
for (const account of accounts) {
20-
// Don't await because this is for performance but not critical
21-
prefetchInboxIdQuery({ account }).catch(captureError);
22-
fetchConversationsQuery({
38+
fetchAllowedConsentConversationsQuery({
2339
account,
2440
caller: "HydrationStateHandler",
2541
})
@@ -36,7 +52,6 @@ export default function HydrationStateHandler() {
3652
});
3753
})
3854
.catch(captureError);
39-
syncConsent(account).catch(captureError);
4055
}
4156

4257
useAppStore.getState().setHydrationDone(true);

0 commit comments

Comments
 (0)