forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {View} from 'react-native'
2import {type AppBskyLabelerDefs} from '@atproto/api'
3import {msg} from '@lingui/core/macro'
4import {useLingui} from '@lingui/react'
5import {Plural, Trans} from '@lingui/react/macro'
6import type React from 'react'
7
8import {getLabelingServiceTitle} from '#/lib/moderation'
9import {sanitizeHandle} from '#/lib/strings/handles'
10import {useLabelerInfoQuery} from '#/state/queries/labeler'
11import {UserAvatar} from '#/view/com/util/UserAvatar'
12import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
13import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
14import {Link as InternalLink, type LinkProps} from '#/components/Link'
15import {RichText} from '#/components/RichText'
16import {Text} from '#/components/Typography'
17import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
18
19type LabelingServiceProps = {
20 labeler: AppBskyLabelerDefs.LabelerViewDetailed
21}
22
23export function Outer({
24 children,
25 style,
26}: React.PropsWithChildren<ViewStyleProp>) {
27 return (
28 <View
29 style={[
30 a.flex_row,
31 a.gap_md,
32 a.w_full,
33 a.p_lg,
34 a.pr_md,
35 a.overflow_hidden,
36 style,
37 ]}>
38 {children}
39 </View>
40 )
41}
42
43export function Avatar({avatar}: {avatar?: string}) {
44 return <UserAvatar type="labeler" size={40} avatar={avatar} />
45}
46
47export function Title({value}: {value: string}) {
48 return (
49 <Text emoji style={[a.text_md, a.font_semi_bold, a.leading_tight]}>
50 {value}
51 </Text>
52 )
53}
54
55export function Description({value, handle}: {value?: string; handle: string}) {
56 const {_} = useLingui()
57 return value ? (
58 <Text numberOfLines={2}>
59 <RichText value={value} />
60 </Text>
61 ) : (
62 <Text emoji style={[a.leading_snug]}>
63 {_(msg`By ${sanitizeHandle(handle, '@')}`)}
64 </Text>
65 )
66}
67
68export function RegionalNotice() {
69 const t = useTheme()
70 return (
71 <View
72 style={[
73 a.flex_row,
74 a.align_center,
75 a.gap_xs,
76 a.pt_2xs,
77 {marginLeft: -2},
78 ]}>
79 <Flag fill={t.atoms.text_contrast_low.color} size="sm" />
80 <Text style={[a.italic, a.leading_snug]}>
81 <Trans>Required in your region</Trans>
82 </Text>
83 </View>
84 )
85}
86
87export function LikeCount({likeCount}: {likeCount: number}) {
88 const t = useTheme()
89 return (
90 <Text
91 style={[
92 a.mt_sm,
93 a.text_sm,
94 t.atoms.text_contrast_medium,
95 {fontWeight: '600'},
96 ]}>
97 <Trans>
98 Liked by <Plural value={likeCount} one="# user" other="# users" />
99 </Trans>
100 </Text>
101 )
102}
103
104export function Content({children}: React.PropsWithChildren<{}>) {
105 const t = useTheme()
106
107 return (
108 <View
109 style={[
110 a.flex_1,
111 a.flex_row,
112 a.gap_md,
113 a.align_center,
114 a.justify_between,
115 ]}>
116 <View style={[a.gap_2xs, a.flex_1]}>{children}</View>
117
118 <ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
119 </View>
120 )
121}
122
123/**
124 * The canonical view for a labeling service. Use this or compose your own.
125 */
126export function Default({
127 labeler,
128 style,
129}: LabelingServiceProps & ViewStyleProp) {
130 return (
131 <Outer style={style}>
132 <Avatar avatar={labeler.creator.avatar} />
133 <Content>
134 <Title
135 value={getLabelingServiceTitle({
136 displayName: labeler.creator.displayName,
137 handle: labeler.creator.handle,
138 })}
139 />
140 <Description
141 value={labeler.creator.description}
142 handle={labeler.creator.handle}
143 />
144 {labeler.likeCount ? <LikeCount likeCount={labeler.likeCount} /> : null}
145 </Content>
146 </Outer>
147 )
148}
149
150export function Link({
151 children,
152 labeler,
153}: LabelingServiceProps & Pick<LinkProps, 'children'>) {
154 const {_} = useLingui()
155
156 return (
157 <InternalLink
158 to={{
159 screen: 'Profile',
160 params: {
161 name: labeler.creator.did,
162 },
163 }}
164 label={_(
165 msg`View the labeling service provided by @${labeler.creator.handle}`,
166 )}>
167 {children}
168 </InternalLink>
169 )
170}
171
172// TODO not finished yet
173export function DefaultSkeleton() {
174 return (
175 <View>
176 <Text>Loading</Text>
177 </View>
178 )
179}
180
181export function Loader({
182 did,
183 loading: LoadingComponent = DefaultSkeleton,
184 error: ErrorComponent,
185 component: Component,
186}: {
187 did: string
188 loading?: React.ComponentType<{}>
189 error?: React.ComponentType<{error: string}>
190 component: React.ComponentType<{
191 labeler: AppBskyLabelerDefs.LabelerViewDetailed
192 }>
193}) {
194 const {isLoading, data, error} = useLabelerInfoQuery({did})
195
196 return isLoading ? (
197 LoadingComponent ? (
198 <LoadingComponent />
199 ) : null
200 ) : error || !data ? (
201 ErrorComponent ? (
202 <ErrorComponent error={error?.message || 'Unknown error'} />
203 ) : null
204 ) : (
205 <Component labeler={data} />
206 )
207}