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