frontend client for gemstone. decentralised workplace app

feat: channels picker initial impl

serenity b45c2a41 137574c7

Changed files
+134
src
components
Navigation
+81
src/components/Navigation/ChannelsPicker/ChannelPickerSpace.tsx
···
+
import { ChannelPickerItem } from "@/components/Navigation/ChannelsPicker/ChannelPickerItem";
+
import { Loading } from "@/components/primitives/Loading";
+
import { Text } from "@/components/primitives/Text";
+
import type { AtUri, Did } from "@/lib/types/atproto";
+
import type { SystemsGmstnDevelopmentChannel } from "@/lib/types/lexicon/systems.gmstn.development.channels";
+
import { getBskyProfile } from "@/queries/get-profile";
+
import { useQuery } from "@tanstack/react-query";
+
import { Image } from "expo-image";
+
import { View } from "react-native";
+
+
export const ChannelPickerSpace = ({
+
space,
+
}: {
+
space: [
+
string,
+
Array<{
+
channel: SystemsGmstnDevelopmentChannel;
+
channelAtUri: AtUri;
+
}>,
+
];
+
}) => {
+
const spaceDid = space[0];
+
+
const { isLoading, data, error } = useQuery({
+
queryKey: ["handle", spaceDid],
+
queryFn: async () => {
+
return await getBskyProfile(spaceDid as Did);
+
},
+
staleTime: Infinity,
+
});
+
+
const channels = space[1];
+
return (
+
<>
+
{isLoading ? (
+
<Loading />
+
) : error ? (
+
<Text>{error.message}</Text>
+
) : (
+
<View style={{ paddingRight: 16 }}>
+
<View
+
style={{
+
display: "flex",
+
flexDirection: "row",
+
gap: 6,
+
alignItems: "center",
+
padding: 4,
+
}}
+
>
+
<Image
+
style={{
+
width: 42,
+
height: 42,
+
borderRadius: 2000000000,
+
}}
+
source={{ uri: data?.avatar ?? spaceDid }}
+
/>
+
<Text>@{data?.handle ?? spaceDid}</Text>
+
</View>
+
<View
+
style={{
+
paddingLeft: 8,
+
display: "flex",
+
gap: 2,
+
paddingTop: 4,
+
paddingBottom: 4,
+
}}
+
>
+
{channels.map(({ channel, channelAtUri }, idx) => (
+
<ChannelPickerItem
+
channel={channel}
+
channelAtUri={channelAtUri}
+
key={idx}
+
/>
+
))}
+
</View>
+
</View>
+
)}
+
</>
+
);
+
};
+53
src/components/Navigation/ChannelsPicker/index.tsx
···
+
import { ChannelPickerSpace } from "@/components/Navigation/ChannelsPicker/ChannelPickerSpace";
+
import { useFacet } from "@/lib/facet";
+
import type { AtUri } from "@/lib/types/atproto";
+
import type { SystemsGmstnDevelopmentChannel } from "@/lib/types/lexicon/systems.gmstn.development.channels";
+
import { useChannelRecords } from "@/providers/authed/ChannelsProvider";
+
import { useCurrentPalette } from "@/providers/ThemeProvider";
+
import { useMemo } from "react";
+
import { View } from "react-native";
+
+
export const ChannelsPicker = () => {
+
const { channels } = useChannelRecords();
+
const { semantic } = useCurrentPalette();
+
const { atoms } = useFacet();
+
+
// we consider a did to be a space.
+
const channelsBySpace = useMemo(
+
() =>
+
channels.reduce(
+
(map, channel) => {
+
const { authority } = channel.channelAtUri;
+
const group = map.get(authority) ?? [];
+
group.push(channel);
+
map.set(authority, group);
+
return map;
+
},
+
new Map<
+
string,
+
Array<{
+
channel: SystemsGmstnDevelopmentChannel;
+
channelAtUri: AtUri;
+
}>
+
>(),
+
),
+
[channels],
+
);
+
+
const spaces = channelsBySpace.entries().toArray();
+
+
return (
+
<View
+
style={{
+
backgroundColor: semantic.backgroundDarker,
+
padding: 12,
+
paddingTop: 6,
+
borderTopRightRadius: atoms.radii.xl,
+
}}
+
>
+
{spaces.map((space) => (
+
<ChannelPickerSpace key={space[0]} space={space} />
+
))}
+
</View>
+
);
+
};