forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2import {View} from 'react-native'
3import {Image} from 'expo-image'
4import {AppBskyGraphStarterpack, AtUri} from '@atproto/api'
5import {msg} from '@lingui/core/macro'
6import {useLingui} from '@lingui/react'
7import {Plural, Trans} from '@lingui/react/macro'
8import {useQueryClient} from '@tanstack/react-query'
9
10import {sanitizeHandle} from '#/lib/strings/handles'
11import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
12import {precacheResolvedUri} from '#/state/queries/resolve-uri'
13import {precacheStarterPack} from '#/state/queries/starter-packs'
14import {useSession} from '#/state/session'
15import {atoms as a, useTheme} from '#/alf'
16import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
17import {
18 Link as BaseLink,
19 type LinkProps as BaseLinkProps,
20} from '#/components/Link'
21import {Text} from '#/components/Typography'
22import * as bsky from '#/types/bsky'
23
24export function Default({
25 starterPack,
26}: {
27 starterPack?: bsky.starterPack.AnyStarterPackView
28}) {
29 if (!starterPack) return null
30 return (
31 <Link starterPack={starterPack}>
32 <Card starterPack={starterPack} />
33 </Link>
34 )
35}
36
37export function Notification({
38 starterPack,
39}: {
40 starterPack?: bsky.starterPack.AnyStarterPackView
41}) {
42 if (!starterPack) return null
43 return (
44 <Link starterPack={starterPack}>
45 <Card starterPack={starterPack} noIcon={true} noDescription={true} />
46 </Link>
47 )
48}
49
50export function Card({
51 starterPack,
52 noIcon,
53 noDescription,
54}: {
55 starterPack: bsky.starterPack.AnyStarterPackView
56 noIcon?: boolean
57 noDescription?: boolean
58}) {
59 const {record, creator, joinedAllTimeCount} = starterPack
60
61 const {_} = useLingui()
62 const t = useTheme()
63 const {currentAccount} = useSession()
64
65 if (
66 !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
67 record,
68 AppBskyGraphStarterpack.isRecord,
69 )
70 ) {
71 return null
72 }
73
74 return (
75 <View style={[a.w_full, a.gap_md]}>
76 <View style={[a.flex_row, a.gap_sm, a.w_full]}>
77 {!noIcon ? <StarterPackIcon width={40} gradient="sky" /> : null}
78 <View style={[a.flex_1]}>
79 <Text
80 emoji
81 style={[a.text_md, a.font_semi_bold, a.leading_snug]}
82 numberOfLines={2}>
83 {record.name}
84 </Text>
85 <Text
86 emoji
87 style={[a.leading_snug, t.atoms.text_contrast_medium]}
88 numberOfLines={1}>
89 {creator?.did === currentAccount?.did
90 ? _(msg`Starter pack by you`)
91 : _(msg`Starter pack by ${sanitizeHandle(creator.handle, '@')}`)}
92 </Text>
93 </View>
94 </View>
95 {!noDescription && record.description ? (
96 <Text emoji numberOfLines={3} style={[a.leading_snug]}>
97 {record.description}
98 </Text>
99 ) : null}
100 {!!joinedAllTimeCount && joinedAllTimeCount >= 50 && (
101 <Text style={[a.font_semi_bold, t.atoms.text_contrast_medium]}>
102 <Trans comment="Number of users (always at least 50) who have joined Bluesky using a specific starter pack">
103 <Plural value={joinedAllTimeCount} other="# users have" /> joined!
104 </Trans>
105 </Text>
106 )}
107 </View>
108 )
109}
110
111export function useStarterPackLink({
112 view,
113}: {
114 view: bsky.starterPack.AnyStarterPackView
115}) {
116 const {_} = useLingui()
117 const qc = useQueryClient()
118 const {rkey, handleOrDid} = React.useMemo(() => {
119 const rkey = new AtUri(view.uri).rkey
120 const {creator} = view
121 return {rkey, handleOrDid: creator.handle || creator.did}
122 }, [view])
123 const precache = () => {
124 precacheResolvedUri(qc, view.creator.handle, view.creator.did)
125 precacheStarterPack(qc, view)
126 }
127
128 return {
129 to: `/starter-pack/${handleOrDid}/${rkey}`,
130 label: bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
131 view.record,
132 AppBskyGraphStarterpack.isRecord,
133 )
134 ? _(msg`Navigate to ${view.record.name}`)
135 : _(msg`Navigate to starter pack`),
136 precache,
137 }
138}
139
140export function Link({
141 starterPack,
142 children,
143}: {
144 starterPack: bsky.starterPack.AnyStarterPackView
145 onPress?: () => void
146 children: BaseLinkProps['children']
147}) {
148 const {_} = useLingui()
149 const queryClient = useQueryClient()
150 const {record} = starterPack
151 const {rkey, handleOrDid} = React.useMemo(() => {
152 const rkey = new AtUri(starterPack.uri).rkey
153 const {creator} = starterPack
154 return {rkey, handleOrDid: creator.handle || creator.did}
155 }, [starterPack])
156
157 if (
158 !bsky.dangerousIsType<AppBskyGraphStarterpack.Record>(
159 record,
160 AppBskyGraphStarterpack.isRecord,
161 )
162 ) {
163 return null
164 }
165
166 return (
167 <BaseLink
168 to={`/starter-pack/${handleOrDid}/${rkey}`}
169 label={_(msg`Navigate to ${record.name}`)}
170 onPress={() => {
171 precacheResolvedUri(
172 queryClient,
173 starterPack.creator.handle,
174 starterPack.creator.did,
175 )
176 precacheStarterPack(queryClient, starterPack)
177 }}
178 style={[a.flex_col, a.align_start]}>
179 {children}
180 </BaseLink>
181 )
182}
183
184export function Embed({
185 starterPack,
186}: {
187 starterPack: bsky.starterPack.AnyStarterPackView
188}) {
189 const t = useTheme()
190 const imageUri = getStarterPackOgCard(starterPack)
191
192 return (
193 <View
194 style={[
195 a.border,
196 a.rounded_sm,
197 a.overflow_hidden,
198 t.atoms.border_contrast_low,
199 ]}>
200 <Link starterPack={starterPack}>
201 <Image
202 source={imageUri}
203 style={[a.w_full, a.aspect_card]}
204 accessibilityIgnoresInvertColors={true}
205 />
206 <View style={[a.px_sm, a.py_md]}>
207 <Card starterPack={starterPack} />
208 </View>
209 </Link>
210 </View>
211 )
212}