Skip to content

Commit 33b94da

Browse files
authored
fix: Many chat fixes (#1601)
1 parent 34ada69 commit 33b94da

File tree

27 files changed

+279
-566
lines changed

27 files changed

+279
-566
lines changed

components/group-avatar.tsx

+102-70
Original file line numberDiff line numberDiff line change
@@ -8,87 +8,50 @@ import { getInboxProfileSocialsQueryConfig } from "@/queries/useInboxProfileSoci
88
import { $globalStyles } from "@/theme/styles";
99
import { ThemedStyle, useAppTheme } from "@/theme/useAppTheme";
1010
import {
11+
getPreferredInboxAddress,
1112
getPreferredInboxAvatar,
1213
getPreferredInboxName,
1314
} from "@/utils/profile";
1415
import { useQueries } from "@tanstack/react-query";
15-
import { ConversationTopic } from "@xmtp/react-native-sdk";
16+
import { ConversationTopic, InboxId } from "@xmtp/react-native-sdk";
1617
import React, { memo, useMemo } from "react";
1718
import { StyleProp, TextStyle, ViewStyle } from "react-native";
1819
import { Avatar } from "./Avatar";
1920

20-
const MAIN_CIRCLE_RADIUS = 50;
21-
const MAX_VISIBLE_MEMBERS = 4;
22-
23-
type Position = { x: number; y: number; size: number };
24-
25-
type IGroupAvatarMember = {
26-
address: string;
27-
uri?: string;
28-
name?: string;
29-
};
30-
31-
type IGroupAvatarDumbProps = {
32-
size?: number;
33-
style?: StyleProp<ViewStyle>;
34-
members?: IGroupAvatarMember[];
35-
};
36-
37-
export const GroupAvatarMembers = memo(function GroupAvatarDumb(
38-
props: IGroupAvatarDumbProps
39-
) {
40-
const { themed, theme } = useAppTheme();
41-
42-
const { size = theme.avatarSize.md, style, members = [] } = props;
43-
44-
const memberCount = members?.length || 0;
21+
/**
22+
* Comp to render a group avatar from a list of inbox ids
23+
*/
24+
export const GroupAvatarInboxIds = memo(function GroupAvatarInboxIds(props: {
25+
inboxIds: InboxId[];
26+
}) {
27+
const { inboxIds } = props;
4528

46-
const positions = useMemo(
47-
() => calculatePositions(memberCount, MAIN_CIRCLE_RADIUS),
48-
[memberCount]
49-
);
29+
const socialsData = useQueries({
30+
queries: inboxIds.map((inboxId) =>
31+
getInboxProfileSocialsQueryConfig({ inboxId })
32+
),
33+
});
5034

51-
return (
52-
<Center style={[{ width: size, height: size }, $container, style]}>
53-
<Center style={[{ width: size, height: size }, $container]}>
54-
<VStack style={themed($background)} />
55-
<Center style={$content}>
56-
{positions.map((pos, index) => {
57-
if (index < MAX_VISIBLE_MEMBERS && index < memberCount) {
58-
return (
59-
<Avatar
60-
key={`avatar-${index}`}
61-
uri={members[index].uri}
62-
name={members[index].name}
63-
size={(pos.size / 100) * size}
64-
style={{
65-
left: (pos.x / 100) * size,
66-
top: (pos.y / 100) * size,
67-
position: "absolute",
68-
}}
69-
/>
70-
);
71-
} else if (
72-
index === MAX_VISIBLE_MEMBERS &&
73-
memberCount > MAX_VISIBLE_MEMBERS
74-
) {
75-
return (
76-
<ExtraMembersIndicator
77-
key={`extra-${index}`}
78-
pos={pos}
79-
extraMembersCount={memberCount - MAX_VISIBLE_MEMBERS}
80-
size={size}
81-
/>
82-
);
35+
const members = useMemo(() => {
36+
return socialsData
37+
.map(({ data: socials }, index): IGroupAvatarMember | null =>
38+
socials
39+
? {
40+
address: getPreferredInboxAddress(socials) ?? "",
41+
uri: getPreferredInboxAvatar(socials),
42+
name: getPreferredInboxName(socials),
8343
}
84-
return null;
85-
})}
86-
</Center>
87-
</Center>
88-
</Center>
89-
);
44+
: null
45+
)
46+
.filter(Boolean);
47+
}, [socialsData]);
48+
49+
return <GroupAvatarUI members={members} />;
9050
});
9151

52+
/**
53+
* Comp to render a group avatar from a group topic (will render the group image if available)
54+
*/
9255
export const GroupAvatar = memo(function GroupAvatar(props: {
9356
groupTopic: ConversationTopic;
9457
size?: "sm" | "md" | "lg";
@@ -166,7 +129,77 @@ export const GroupAvatar = memo(function GroupAvatar(props: {
166129
return <Avatar uri={group.imageUrlSquare} size={sizeNumber} />;
167130
}
168131

169-
return <GroupAvatarMembers members={memberData} size={sizeNumber} />;
132+
return <GroupAvatarUI members={memberData} size={sizeNumber} />;
133+
});
134+
135+
const MAIN_CIRCLE_RADIUS = 50;
136+
const MAX_VISIBLE_MEMBERS = 4;
137+
138+
type Position = { x: number; y: number; size: number };
139+
140+
type IGroupAvatarMember = {
141+
address: string;
142+
uri?: string;
143+
name?: string;
144+
};
145+
146+
type IGroupAvatarUIProps = {
147+
size?: number;
148+
style?: StyleProp<ViewStyle>;
149+
members?: IGroupAvatarMember[];
150+
};
151+
152+
const GroupAvatarUI = memo(function GroupAvatarUI(props: IGroupAvatarUIProps) {
153+
const { themed, theme } = useAppTheme();
154+
155+
const { size = theme.avatarSize.md, style, members = [] } = props;
156+
157+
const memberCount = members?.length || 0;
158+
159+
const positions = useMemo(
160+
() => calculatePositions(memberCount, MAIN_CIRCLE_RADIUS),
161+
[memberCount]
162+
);
163+
164+
return (
165+
<Center style={[{ width: size, height: size }, $container, style]}>
166+
<Center style={[{ width: size, height: size }, $container]}>
167+
<VStack style={themed($background)} />
168+
<Center style={$content}>
169+
{positions.map((pos, index) => {
170+
if (index < MAX_VISIBLE_MEMBERS && index < memberCount) {
171+
return (
172+
<Avatar
173+
key={`avatar-${index}`}
174+
uri={members[index].uri}
175+
name={members[index].name}
176+
size={(pos.size / 100) * size}
177+
style={{
178+
left: (pos.x / 100) * size,
179+
top: (pos.y / 100) * size,
180+
position: "absolute",
181+
}}
182+
/>
183+
);
184+
} else if (
185+
index === MAX_VISIBLE_MEMBERS &&
186+
memberCount > MAX_VISIBLE_MEMBERS
187+
) {
188+
return (
189+
<ExtraMembersIndicator
190+
key={`extra-${index}`}
191+
pos={pos}
192+
extraMembersCount={memberCount - MAX_VISIBLE_MEMBERS}
193+
size={size}
194+
/>
195+
);
196+
}
197+
return null;
198+
})}
199+
</Center>
200+
</Center>
201+
</Center>
202+
);
170203
});
171204

172205
const calculatePositions = (
@@ -256,7 +289,6 @@ const $background: ThemedStyle<ViewStyle> = ({ colors }) => ({
256289
...$globalStyles.absoluteFill,
257290
backgroundColor: colors.fill.minimal,
258291
borderRadius: 999,
259-
opacity: 0.4,
260292
});
261293

262294
const $extraMembersContainer: ThemedStyle<ViewStyle> = ({ colors }) => ({

components/swipeable.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@ export function Swipeable({
104104
progress: SharedValue<number>,
105105
drag: SharedValue<number>,
106106
swipeable: SwipeableMethods
107-
) =>
108-
renderLeftActions?.({
107+
) => {
108+
return renderLeftActions?.({
109109
progressAnimatedValue: progress,
110110
dragAnimatedValue: drag,
111111
swipeable,
112-
}),
112+
});
113+
},
113114
[renderLeftActions]
114115
);
115116

@@ -118,12 +119,13 @@ export function Swipeable({
118119
progress: SharedValue<number>,
119120
drag: SharedValue<number>,
120121
swipeable: SwipeableMethods
121-
) =>
122-
renderRightActions?.({
122+
) => {
123+
return renderRightActions?.({
123124
progressAnimatedValue: progress,
124125
dragAnimatedValue: drag,
125126
swipeable,
126-
}),
127+
});
128+
},
127129
[renderRightActions]
128130
);
129131

containers/GroupScreenImage.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { GroupAvatarMembers } from "@/components/group-avatar";
1+
import { GroupAvatar } from "@/components/group-avatar";
2+
import { Center } from "@/design-system/Center";
23
import { useGroupMembersInfoForCurrentAccount } from "@/hooks/use-group-members-info-for-current-account";
34
import Button from "@components/Button/Button";
45
import { useCurrentAccount } from "@data/store/accountsStore";
@@ -70,10 +71,9 @@ export const GroupScreenImage: FC<GroupScreenImageProps> = ({ topic }) => {
7071
return (
7172
<List.Section>
7273
<View style={{ alignItems: "center" }}>
73-
<GroupAvatarMembers
74-
members={groupMembersInfo}
75-
style={{ marginBottom: 10, marginTop: 23 }}
76-
/>
74+
<Center style={{ marginBottom: 10, marginTop: 23 }}>
75+
<GroupAvatar groupTopic={topic} size="md" />
76+
</Center>
7777
{canEditGroupImage && (
7878
<Button
7979
variant="text"

design-system/Icon/Icon.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export function Icon(props: IIconProps) {
8888
style,
8989
size = defaultSize,
9090
color = defaultColor,
91-
weight,
9291
...rest
9392
} = props;
9493

design-system/Icon/Icon.types.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,16 @@ export type IIconProps = RequireExactlyOne<{
9292
* @default 24
9393
*/
9494
size?: number;
95-
9695
/**
97-
* @deprecated
98-
* Weight property is no longer supported and will be removed in the next major version.
99-
* @see Use different icon names for different weights instead.
96+
* Weight of the icon
10097
*/
101-
weight?: string;
98+
weight?:
99+
| "ultralight"
100+
| "light"
101+
| "thin"
102+
| "regular"
103+
| "medium"
104+
| "semibold"
105+
| "bold"
106+
| "heavy";
102107
};

design-system/IconButton/IconButton.props.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// IconButton.props.ts
2-
import { IIconName } from "@design-system/Icon/Icon.types";
2+
import { IIconName, IIconProps } from "@design-system/Icon/Icon.types";
33
import {
44
PressableProps as RNPressableProps,
55
StyleProp,
@@ -23,6 +23,11 @@ export type IIconButtonProps = {
2323
*/
2424
iconName?: IIconName;
2525

26+
/**
27+
* The weight of the icon.
28+
*/
29+
iconWeight?: IIconProps["weight"];
30+
2631
/**
2732
* The variant of the button.
2833
* Determines the button's overall style.

design-system/IconButton/IconButton.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const IconButton = React.forwardRef(function IconButton(
2424
const {
2525
icon,
2626
iconName,
27+
iconWeight,
2728
variant = "fill",
2829
size = "md",
2930
action = "primary",
@@ -126,6 +127,7 @@ export const IconButton = React.forwardRef(function IconButton(
126127
ref={ref}
127128
picto={iconName}
128129
style={iconStyle({ pressed, hovered })}
130+
weight={iconWeight}
129131
{...iconProps({ pressed, hovered })}
130132
/>
131133
);

design-system/Text/Text.styles.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Theme, ThemedStyle } from "@theme/useAppTheme";
44
import { IInvertedTextColors, ITextColors, IWeights } from "./Text.props";
55

66
export const textSizeStyles = {
7+
["5xl"]: { fontSize: 80, lineHeight: 88 } satisfies TextStyle,
78
["3xl"]: { fontSize: 48, lineHeight: 60 } satisfies TextStyle,
89
xxl: { fontSize: 40, lineHeight: 44 } satisfies TextStyle,
910
xl: { fontSize: 32, lineHeight: 36 } satisfies TextStyle, // Made up, need to confirm with Andrew once we have the design

features/conversation-list/conversation-list-item/conversation-list-item-swipeable/conversation-list-item-swipeable-toggle-read-action.tsx

+26-6
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,43 @@ import { ISwipeableRenderActionsArgs } from "@/components/swipeable";
22
import { useConversationIsUnread } from "@/features/conversation-list/hooks/use-conversation-is-unread";
33
import { useAppTheme } from "@/theme/useAppTheme";
44
import { ConversationTopic } from "@xmtp/react-native-sdk";
5-
import React, { memo } from "react";
5+
import React, { memo, useState } from "react";
6+
import { runOnJS, useAnimatedReaction } from "react-native-reanimated";
67
import { ConversationListItemSwipeableAction } from "./conversation-list-item-swipeable-action";
78

9+
type IconType = "checkmark.message" | "message.badge";
10+
811
export const ToggleUnreadSwipeableAction = memo(
912
(props: ISwipeableRenderActionsArgs & { topic: ConversationTopic }) => {
13+
const { progressAnimatedValue, topic } = props;
1014
const { theme } = useAppTheme();
15+
const { isUnread } = useConversationIsUnread({ topic });
16+
17+
// Track which icon to display. Needed to control when the icon changes
18+
const [displayIcon, setDisplayIcon] = useState<IconType>(
19+
isUnread ? "checkmark.message" : "message.badge"
20+
);
1121

12-
const { isUnread } = useConversationIsUnread({
13-
topic: props.topic,
14-
});
22+
// Only update the icon when the swipe animation is almost complete (progress < 0.1)
23+
// This prevents the icon from changing while the swipe action is still visible
24+
useAnimatedReaction(
25+
() => progressAnimatedValue.value,
26+
(currentValue) => {
27+
if (currentValue < 0.1) {
28+
runOnJS(setDisplayIcon)(
29+
isUnread ? "checkmark.message" : "message.badge"
30+
);
31+
}
32+
},
33+
[isUnread]
34+
);
1535

1636
return (
1737
<ConversationListItemSwipeableAction
18-
icon={isUnread ? "checkmark.message" : "message.badge"}
38+
icon={displayIcon}
1939
color={theme.colors.fill.secondary}
2040
iconColor={theme.colors.global.white}
21-
actionProgress={props.progressAnimatedValue}
41+
actionProgress={progressAnimatedValue}
2242
/>
2343
);
2444
}

0 commit comments

Comments
 (0)