Skip to content

Commit ddc2d58

Browse files
Merge upstream develop
2 parents 1c21f78 + 70461e0 commit ddc2d58

66 files changed

Lines changed: 4743 additions & 854 deletions

Some content is hidden

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

.gitlab/ci/build_rs_core.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ rs-core-clippy:
4848
- cd packages/rs-core
4949
- just clippy
5050

51+
rs-core-tests:
52+
extends: .rust-check
53+
script:
54+
- cd packages/rs-core
55+
- just test
56+
57+
rs-common-format-check:
58+
extends: .rust-check
59+
script:
60+
- cd packages/rs-core
61+
- cargo fmt --check
62+
63+
rs-common-clippy:
64+
extends: .rust-check
65+
script:
66+
- cd packages/rs-common
67+
- cargo clippy
68+
69+
rs-common-tests:
70+
extends: .rust-check
71+
script:
72+
- cd packages/rs-common
73+
- cargo test
74+
5175
# rs-core-build:
5276
# extends: .rs-core-workflow
5377
# stage: build

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/polycentric/src/common/components/List/List.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,14 @@ function WebFeedViewer<T>({
159159
data,
160160
renderItem,
161161
keyExtractor,
162+
HeaderComponent,
162163
ListHeaderComponent,
163164
ListFooterComponent,
164165
ListEmptyComponent,
165166
onEndReached,
166167
contentContainerStyle,
167168
stickyHeaderIndices,
168-
}: FlashListProps<T>) {
169+
}: ListProps<T>) {
169170
const sentinelRef = useRef<View>(null);
170171
const items = (data as readonly T[] | null | undefined) ?? [];
171172
const [visibleCount, setVisibleCount] = useState(WEB_INITIAL_VISIBLE);
@@ -203,6 +204,7 @@ function WebFeedViewer<T>({
203204

204205
return (
205206
<View style={contentContainerStyle}>
207+
{renderNode(HeaderComponent)}
206208
{renderNode(ListHeaderComponent)}
207209
{isEmpty
208210
? renderNode(ListEmptyComponent)

apps/polycentric/src/features/feed/FeedList.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { isWeb } from '@/src/common/util/platform';
1010
import { Text } from '@/src/common/components/primitives';
1111
import { Atoms, useTheme } from '@/src/common/theme';
1212
import { Post } from '../post/Post';
13+
import { PostSkeletonList } from '../post/PostSkeleton';
1314

1415
export type FeedListProps = Omit<ListProps<PostData>, 'data' | 'renderItem'> & {
1516
feed: FeedHookResult;
@@ -38,7 +39,9 @@ export default function FeedList({
3839
keyExtractor={keyExtractor}
3940
data={feed.items}
4041
ListEmptyComponent={
41-
!feed.isLoading ? (
42+
feed.isLoading ? (
43+
<PostSkeletonList />
44+
) : (
4245
<View
4346
style={[
4447
Atoms.flex_1,
@@ -49,7 +52,7 @@ export default function FeedList({
4952
>
5053
<Text color="neutral_500">{emptyMessage}</Text>
5154
</View>
52-
) : null
55+
)
5356
}
5457
ListFooterComponent={
5558
feed.hasMore && feed.items.length > 0 ? (

apps/polycentric/src/features/feed/hooks/useExploreFeed.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function useExploreFeed(options?: {
2222
new Query.GetExploreFeed({
2323
identity: identity === '' ? undefined : identity,
2424
}),
25+
undefined,
26+
enabled,
2527
);
2628

2729
const items = useMemo(() => {

apps/polycentric/src/features/feed/hooks/useFollowingFeed.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export function useFollowingFeed(options?: {
2020
const query = useQuery(
2121
feedQueryKeys.following(),
2222
new Query.GetFollowingFeed({ followerIdentity }),
23+
undefined,
24+
enabled,
2325
);
2426

2527
const items = useMemo(() => {

apps/polycentric/src/features/follow/hooks/useFollows.ts

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const useFollows = create<FollowsState>((set, get) => ({
4343
* Creates the Follow event and syncs
4444
*/
4545
async addFollow(client, identity) {
46+
const follows = get().follows;
47+
4648
const content = v2.Content.create({
4749
contentBody: {
4850
oneofKind: 'follow',
@@ -53,10 +55,18 @@ const useFollows = create<FollowsState>((set, get) => ({
5355
await client.contentManager.save(content);
5456
const event = await client.buildEvent(content, COLLECTION.GRAPH);
5557
const signedEvent = await client.signEvent(event);
56-
await client.commitEvent(signedEvent, content);
57-
await client.sync();
5858

59-
set({ follows: new Map(get().follows).set(identity, true) });
59+
// Optimistically upate the state
60+
set({ follows: new Map(follows).set(identity, true) });
61+
62+
try {
63+
await client.commitEvent(signedEvent, content);
64+
await client.sync();
65+
} catch (err) {
66+
console.error(err);
67+
// revert the change
68+
set({ follows });
69+
}
6070
},
6171
/**
6272
* Creates a Delete event for the last know Follow event and sync
@@ -65,6 +75,8 @@ const useFollows = create<FollowsState>((set, get) => ({
6575
const self = client.activeIdentityKey;
6676
if (!self) return;
6777

78+
const follows = get().follows;
79+
6880
const bundles = client.listValidEvents(self, COLLECTION.GRAPH);
6981

7082
// Tombstone every active Follow event this identity wrote that targets
@@ -77,30 +89,37 @@ const useFollows = create<FollowsState>((set, get) => ({
7789
entry !== null && entry.identity === identity,
7890
);
7991

80-
for (const { event } of targets) {
81-
if (!event.key) continue;
82-
const deleteContent = v2.Content.create({
83-
contentBody: {
84-
oneofKind: 'delete',
85-
delete: { eventKey: event.key },
86-
},
87-
});
88-
await client.contentManager.save(deleteContent);
89-
const deleteEvent = await client.buildEvent(
90-
deleteContent,
91-
COLLECTION.GRAPH,
92-
);
93-
const signedDelete = await client.signEvent(deleteEvent);
94-
await client.commitEvent(signedDelete, deleteContent);
92+
// Optimistically upate the state
93+
const next = new Map(follows);
94+
next.delete(identity);
95+
set({ follows: next });
96+
97+
try {
98+
for (const { event } of targets) {
99+
if (!event.key) continue;
100+
const deleteContent = v2.Content.create({
101+
contentBody: {
102+
oneofKind: 'delete',
103+
delete: { eventKey: event.key },
104+
},
105+
});
106+
await client.contentManager.save(deleteContent);
107+
const deleteEvent = await client.buildEvent(
108+
deleteContent,
109+
COLLECTION.GRAPH,
110+
);
111+
const signedDelete = await client.signEvent(deleteEvent);
112+
await client.commitEvent(signedDelete, deleteContent);
113+
}
114+
} catch (err) {
115+
console.error(err);
116+
// Revert the state change
117+
set({ follows });
95118
}
96119

97120
if (targets.length > 0) {
98121
await client.sync();
99122
}
100-
101-
const next = new Map(get().follows);
102-
next.delete(identity);
103-
set({ follows: next });
104123
},
105124
/**
106125
* Returns the synced and valid (post tombstoned) Follow events

apps/polycentric/src/features/onboarding/OnboardingWelcomeScreen.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ export default function OnboardingWelcomeScreen() {
2020
Atoms.inset_0,
2121
Atoms.justify_center,
2222
Atoms.items_center,
23-
Atoms.p_2xl,
23+
Atoms.gap_2xl,
24+
Atoms.p_3xl,
2425
]}
2526
>
2627
<Image
2728
source={LOGO_WITH_TEXT}
2829
contentFit="contain"
29-
style={{ width: '100%', height: '100%' }}
30+
style={{
31+
width: '100%',
32+
aspectRatio: 1,
33+
}}
3034
/>
3135
</View>
3236
<View style={[Atoms.gap_md, Atoms.w_full, Atoms.mt_auto]}>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Atoms, useTheme, withHexOpacity } from '@/src/common/theme';
2+
import { useEffect } from 'react';
3+
import { View } from 'react-native';
4+
import Animated, {
5+
Easing,
6+
useAnimatedStyle,
7+
useSharedValue,
8+
withRepeat,
9+
withTiming,
10+
} from 'react-native-reanimated';
11+
12+
const AVATAR_SIZE = 40;
13+
const PULSE_MS = 900;
14+
15+
function useShimmerOpacity() {
16+
const opacity = useSharedValue(0.5);
17+
useEffect(() => {
18+
opacity.value = withRepeat(
19+
withTiming(1, { duration: PULSE_MS, easing: Easing.inOut(Easing.ease) }),
20+
-1,
21+
true,
22+
);
23+
}, [opacity]);
24+
return useAnimatedStyle(() => ({ opacity: opacity.value }));
25+
}
26+
27+
function Bar({
28+
width,
29+
height = 12,
30+
}: {
31+
width: number | `${number}%`;
32+
height?: number;
33+
}) {
34+
const { theme } = useTheme();
35+
return (
36+
<View
37+
style={{
38+
width,
39+
height,
40+
borderRadius: height / 2,
41+
backgroundColor: withHexOpacity(theme.palette.neutral_500, '20'),
42+
}}
43+
/>
44+
);
45+
}
46+
47+
export function PostSkeleton() {
48+
const { theme } = useTheme();
49+
const animatedStyle = useShimmerOpacity();
50+
51+
return (
52+
<Animated.View
53+
style={[
54+
Atoms.w_full,
55+
Atoms.px_md,
56+
Atoms.pt_md,
57+
Atoms.pb_md,
58+
Atoms.flex_row,
59+
Atoms.gap_md,
60+
animatedStyle,
61+
{
62+
borderBottomWidth: 1,
63+
borderBottomColor: withHexOpacity(theme.palette.neutral_500, '20'),
64+
},
65+
]}
66+
>
67+
<View
68+
style={{
69+
width: AVATAR_SIZE,
70+
height: AVATAR_SIZE,
71+
borderRadius: AVATAR_SIZE / 2,
72+
backgroundColor: withHexOpacity(theme.palette.neutral_500, '20'),
73+
}}
74+
/>
75+
<View style={[Atoms.flex_1, Atoms.gap_sm]}>
76+
<View style={[Atoms.flex_row, Atoms.gap_sm, Atoms.align_center]}>
77+
<Bar width={120} />
78+
<Bar width={60} />
79+
<Bar width={40} />
80+
</View>
81+
<Bar width="100%" />
82+
<Bar width="80%" />
83+
<View style={[Atoms.flex_row, Atoms.gap_lg, Atoms.mt_sm]}>
84+
<Bar width={28} height={14} />
85+
<Bar width={28} height={14} />
86+
<Bar width={28} height={14} />
87+
</View>
88+
</View>
89+
</Animated.View>
90+
);
91+
}
92+
93+
export function PostSkeletonList({ count = 6 }: { count?: number }) {
94+
return (
95+
<View>
96+
{Array.from({ length: count }).map((_, i) => (
97+
<PostSkeleton key={i} />
98+
))}
99+
</View>
100+
);
101+
}

apps/polycentric/src/features/profile/ProfileHeader.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function ProfileHeaderInner({ bannerColors, onBack }: ProfileHeaderProps) {
3434

3535
const fallbackUsername = useUsername(identityKey);
3636
const profile = useProfile(identityKey, { fetchMode: FetchMode.Default });
37+
3738
const username = profile.name ?? fallbackUsername;
3839

3940
const short = identityKey ? shortenIdentityId(identityKey) : '...';
@@ -42,6 +43,8 @@ function ProfileHeaderInner({ bannerColors, onBack }: ProfileHeaderProps) {
4243
if (identityKey) router.push(Routes.tabs.editProfile(identityKey));
4344
}, [identityKey]);
4445

46+
if (profile.isLoading && !profile.name) return undefined;
47+
4548
return (
4649
<>
4750
<View style={{ position: 'relative' }}>

0 commit comments

Comments
 (0)