frontend client for gemstone. decentralised workplace app
1import { Message } from "@/components/Chat/Message";
2import { Loading } from "@/components/primitives/Loading";
3import { Text } from "@/components/primitives/Text";
4import { useFacet } from "@/lib/facet";
5import { useChannel } from "@/lib/hooks/useChannel";
6import type { AtUri } from "@/lib/types/atproto";
7import { useChannelRecordByAtUriObject } from "@/providers/authed/ChannelsProvider";
8import { useProfile } from "@/providers/authed/ProfileProvider";
9import { useCurrentPalette } from "@/providers/ThemeProvider";
10import { ArrowUp, Dot, Hash } from "lucide-react-native";
11import { useState } from "react";
12import { View, TextInput, FlatList, Pressable } from "react-native";
13
14export const Chat = ({ channelAtUri }: { channelAtUri: AtUri }) => {
15 const [inputText, setInputText] = useState("");
16 const record = useChannelRecordByAtUriObject(channelAtUri);
17 const { semantic } = useCurrentPalette();
18 const { typography, atoms } = useFacet();
19 const channel = useChannel(channelAtUri);
20 const { isLoading } = useProfile();
21
22 if (!channel) return <></>;
23
24 const { messages, sendMessageToChannel, isConnected } = channel;
25
26 const handleSend = () => {
27 if (inputText.trim()) {
28 sendMessageToChannel(inputText);
29 setInputText("");
30 }
31 };
32
33 if (!record)
34 return (
35 <View>
36 <Text>
37 Something has gone wrong.Could not resolve channel record
38 from given at:// URI.
39 </Text>
40 </View>
41 );
42
43 return isLoading ? (
44 <Loading />
45 ) : (
46 <View
47 style={{
48 flex: 1,
49 flexDirection: "column",
50 justifyContent: "center",
51 alignItems: "stretch",
52 backgroundColor: semantic.backgroundDark,
53 }}
54 >
55 <View
56 style={{
57 paddingHorizontal: 16,
58 paddingVertical: 12,
59 flexDirection: "row",
60 boxShadow: atoms.boxShadows.lg,
61 justifyContent: "space-between",
62 backgroundColor: semantic.background,
63 alignItems: "center",
64 }}
65 >
66 <View
67 style={{
68 flexDirection: "row",
69 gap: 2,
70 alignItems: "center",
71 }}
72 >
73 <View
74 style={{
75 flexDirection: "row",
76 gap: 4,
77 alignItems: "center",
78 }}
79 >
80 <Hash
81 style={{
82 height: 16,
83 width: 16,
84 }}
85 color={semantic.border}
86 />
87 <Text
88 style={[
89 {
90 color: semantic.textSecondary,
91 },
92 typography.sizes.sm,
93 ]}
94 >
95 {record.channel.name}
96 </Text>
97 </View>
98 <View
99 style={{
100 flexDirection: "row",
101 gap: 2,
102 alignItems: "center",
103 }}
104 >
105 <Dot
106 style={{
107 height: 24,
108 width: 24,
109 }}
110 color={semantic.border}
111 />
112 <Text
113 style={[
114 {
115 color: semantic.textTertiary,
116 },
117 typography.sizes.sm,
118 ]}
119 >
120 {record.channel.topic}
121 </Text>
122 </View>
123 </View>
124 <View
125 style={{
126 width: 8,
127 height: 8,
128 borderRadius: atoms.radii.full,
129 backgroundColor: isConnected
130 ? semantic.positive
131 : semantic.negative,
132 }}
133 />
134 </View>
135
136 <FlatList
137 inverted
138 data={messages.toReversed()}
139 renderItem={({ item }) => <Message message={item} />}
140 keyExtractor={(_, index) => index.toString()}
141 contentContainerStyle={{
142 paddingLeft: 20,
143 flex: 1,
144 gap: 2,
145 }}
146 showsVerticalScrollIndicator={false}
147 />
148
149 <View
150 style={{
151 flexDirection: "row",
152 padding: 16,
153 alignItems: "center",
154 }}
155 >
156 <TextInput
157 editable={isConnected}
158 style={[
159 {
160 flex: 1,
161 borderWidth: 1,
162 borderColor: semantic.borderVariant,
163 borderRadius: 8,
164 paddingHorizontal: 12,
165 paddingVertical: 12,
166 marginRight: 8,
167 color: semantic.text,
168 outline: "0",
169 cursor: isConnected ? "" : "not-allowed",
170 fontFamily: typography.families.primary,
171 },
172 typography.weights.byName.extralight,
173 typography.sizes.sm,
174 ]}
175 cursorColor={
176 isConnected ? semantic.textTertiary : "transparent"
177 }
178 selectionColor={semantic.primary}
179 value={inputText}
180 onChangeText={setInputText}
181 placeholder={`Message #${record.channel.name}`}
182 placeholderTextColor={semantic.textPlaceholder}
183 onSubmitEditing={handleSend}
184 // eslint-disable-next-line @typescript-eslint/no-deprecated -- can't get it working with the new prop.
185 blurOnSubmit={false}
186 />
187 <Pressable
188 style={{
189 backgroundColor: inputText.trim()
190 ? semantic.primary
191 : semantic.border,
192 borderRadius: atoms.radii.full,
193 justifyContent: "center",
194 height: 36,
195 width: 36,
196 alignItems: "center",
197 }}
198 onPress={handleSend}
199 disabled={!isConnected}
200 >
201 <ArrowUp height={20} width={20} />
202 </Pressable>
203 </View>
204 </View>
205 );
206};