Cull unused files (#11029)
This commit is contained in:
@@ -927,11 +927,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/lib/hooks/useTabFocusEffect.ts": {
|
|
||||||
"react-hooks/set-state-in-effect": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/lib/hooks/useToggleMutationQueue.ts": {
|
"src/lib/hooks/useToggleMutationQueue.ts": {
|
||||||
"@typescript-eslint/no-explicit-any": {
|
"@typescript-eslint/no-explicit-any": {
|
||||||
"count": 2
|
"count": 2
|
||||||
@@ -1073,17 +1068,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/screens/Bookmarks/index.tsx": {
|
|
||||||
"@typescript-eslint/no-explicit-any": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"@typescript-eslint/no-misused-promises": {
|
|
||||||
"count": 3
|
|
||||||
},
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": {
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/screens/Deactivated.tsx": {
|
"src/screens/Deactivated.tsx": {
|
||||||
"@typescript-eslint/no-explicit-any": {
|
"@typescript-eslint/no-explicit-any": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@@ -2291,14 +2275,6 @@
|
|||||||
"count": 3
|
"count": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/view/com/util/ViewSelector.tsx": {
|
|
||||||
"@typescript-eslint/no-explicit-any": {
|
|
||||||
"count": 6
|
|
||||||
},
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/view/com/util/Views.tsx": {
|
"src/view/com/util/Views.tsx": {
|
||||||
"@typescript-eslint/no-explicit-any": {
|
"@typescript-eslint/no-explicit-any": {
|
||||||
"count": 1
|
"count": 1
|
||||||
@@ -2314,14 +2290,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/view/screens/Debug.tsx": {
|
|
||||||
"@typescript-eslint/no-explicit-any": {
|
|
||||||
"count": 1
|
|
||||||
},
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": {
|
|
||||||
"count": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/view/screens/Feeds.tsx": {
|
"src/view/screens/Feeds.tsx": {
|
||||||
"@typescript-eslint/no-floating-promises": {
|
"@typescript-eslint/no-floating-promises": {
|
||||||
"count": 3
|
"count": 3
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import {Children, cloneElement, Fragment, isValidElement} from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
|
|
||||||
import {atoms, useTheme} from '#/alf'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOT FINISHED, just here as a reference
|
|
||||||
*/
|
|
||||||
export function InputGroup(props: React.PropsWithChildren<{}>) {
|
|
||||||
const t = useTheme()
|
|
||||||
const children = Children.toArray(props.children)
|
|
||||||
const total = children.length
|
|
||||||
return (
|
|
||||||
<View style={[atoms.w_full]}>
|
|
||||||
{children.map((child, i) => {
|
|
||||||
return isValidElement(child) ? (
|
|
||||||
<Fragment key={i}>
|
|
||||||
{i > 0 ? (
|
|
||||||
<View
|
|
||||||
style={[atoms.border_b, {borderColor: t.palette.contrast_500}]}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{cloneElement(child, {
|
|
||||||
// @ts-ignore
|
|
||||||
style: [
|
|
||||||
// @ts-ignore
|
|
||||||
...(Array.isArray(child.props?.style)
|
|
||||||
? // @ts-ignore
|
|
||||||
child.props.style
|
|
||||||
: // @ts-ignore
|
|
||||||
[child.props.style || {}]),
|
|
||||||
{
|
|
||||||
borderTopLeftRadius: i > 0 ? 0 : undefined,
|
|
||||||
borderTopRightRadius: i > 0 ? 0 : undefined,
|
|
||||||
borderBottomLeftRadius: i < total - 1 ? 0 : undefined,
|
|
||||||
borderBottomRightRadius: i < total - 1 ? 0 : undefined,
|
|
||||||
borderBottomWidth: i < total - 1 ? 0 : undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})}
|
|
||||||
</Fragment>
|
|
||||||
) : null
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import {View} from 'react-native'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {useCleanError} from '#/lib/hooks/useCleanError'
|
|
||||||
import {atoms as a, web} from '#/alf'
|
|
||||||
import {Admonition} from '#/components/Admonition'
|
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
|
||||||
import * as Dialog from '#/components/Dialog'
|
|
||||||
import {Loader} from '#/components/Loader'
|
|
||||||
import * as Toast from '#/components/Toast'
|
|
||||||
import {Span, Text} from '#/components/Typography'
|
|
||||||
import {IS_NATIVE} from '#/env'
|
|
||||||
import {useUpdateLiveEventPreferences} from '#/features/liveEvents/preferences'
|
|
||||||
import {
|
|
||||||
type LiveEventFeed,
|
|
||||||
type LiveEventFeedMetricContext,
|
|
||||||
} from '#/features/liveEvents/types'
|
|
||||||
|
|
||||||
export {useDialogControl} from '#/components/Dialog'
|
|
||||||
|
|
||||||
export function LiveEventFeedOptionsMenu({
|
|
||||||
control,
|
|
||||||
feed,
|
|
||||||
metricContext,
|
|
||||||
}: {
|
|
||||||
control: Dialog.DialogControlProps
|
|
||||||
feed: LiveEventFeed
|
|
||||||
metricContext: LiveEventFeedMetricContext
|
|
||||||
}) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
return (
|
|
||||||
<Dialog.Outer control={control}>
|
|
||||||
<Dialog.Handle />
|
|
||||||
<Dialog.ScrollableInner
|
|
||||||
label={_(msg`Configure live event banner`)}
|
|
||||||
style={[web({maxWidth: 400})]}>
|
|
||||||
<Inner control={control} feed={feed} metricContext={metricContext} />
|
|
||||||
<Dialog.Close />
|
|
||||||
</Dialog.ScrollableInner>
|
|
||||||
</Dialog.Outer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Inner({
|
|
||||||
control,
|
|
||||||
feed,
|
|
||||||
metricContext,
|
|
||||||
}: {
|
|
||||||
control: Dialog.DialogControlProps
|
|
||||||
feed: LiveEventFeed
|
|
||||||
metricContext: LiveEventFeedMetricContext
|
|
||||||
}) {
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {
|
|
||||||
isPending,
|
|
||||||
mutate: update,
|
|
||||||
error: rawError,
|
|
||||||
variables,
|
|
||||||
} = useUpdateLiveEventPreferences({
|
|
||||||
feed,
|
|
||||||
metricContext,
|
|
||||||
onUpdateSuccess({undoAction}) {
|
|
||||||
Toast.show(
|
|
||||||
<Toast.Outer>
|
|
||||||
<Toast.Icon />
|
|
||||||
<Toast.Text>
|
|
||||||
<Trans>Your live event preferences have been updated.</Trans>
|
|
||||||
</Toast.Text>
|
|
||||||
{undoAction && (
|
|
||||||
<Toast.Action
|
|
||||||
label={_(msg`Undo`)}
|
|
||||||
onPress={() => {
|
|
||||||
if (undoAction) {
|
|
||||||
update(undoAction)
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<Trans>Undo</Trans>
|
|
||||||
</Toast.Action>
|
|
||||||
)}
|
|
||||||
</Toast.Outer>,
|
|
||||||
{type: 'success'},
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If there is no `undoAction`, it means that the action was already
|
|
||||||
* undone, and therefore the menu would have been closed prior to the
|
|
||||||
* undo happening.
|
|
||||||
*/
|
|
||||||
if (undoAction) {
|
|
||||||
control.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const cleanError = useCleanError()
|
|
||||||
const error = rawError ? cleanError(rawError) : undefined
|
|
||||||
|
|
||||||
const isHidingFeed = variables?.type === 'hideFeed' && isPending
|
|
||||||
const isHidingAllFeeds = variables?.type === 'toggleHideAllFeeds' && isPending
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[a.gap_lg]}>
|
|
||||||
<View style={[a.gap_sm]}>
|
|
||||||
<Text style={[a.text_2xl, a.font_semi_bold, a.leading_snug]}>
|
|
||||||
<Trans>Live event options</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={[a.text_md, a.leading_snug]}>
|
|
||||||
<Trans>
|
|
||||||
Live events appear occasionally when something exciting is
|
|
||||||
happening. If you'd like, you can hide this particular event, or all
|
|
||||||
events for this placement in your app interface.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={[a.text_md, a.leading_snug]}>
|
|
||||||
<Trans>
|
|
||||||
If you choose to hide all events, you can always re-enable them from{' '}
|
|
||||||
<Span style={[a.font_semi_bold]}>Settings → Content & Media</Span>.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={[a.gap_sm]}>
|
|
||||||
<Button
|
|
||||||
label={_(msg`Hide this event`)}
|
|
||||||
size="large"
|
|
||||||
color="primary_subtle"
|
|
||||||
onPress={() => {
|
|
||||||
update({type: 'hideFeed', id: feed.id})
|
|
||||||
}}>
|
|
||||||
<ButtonText>
|
|
||||||
<Trans>Hide this event</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
{isHidingFeed && <ButtonIcon icon={Loader} />}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
label={_(msg`Hide all events`)}
|
|
||||||
size="large"
|
|
||||||
color="secondary"
|
|
||||||
onPress={() => {
|
|
||||||
update({type: 'toggleHideAllFeeds'})
|
|
||||||
}}>
|
|
||||||
<ButtonText>
|
|
||||||
<Trans>Hide all events</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
{isHidingAllFeeds && <ButtonIcon icon={Loader} />}
|
|
||||||
</Button>
|
|
||||||
{IS_NATIVE && (
|
|
||||||
<Button
|
|
||||||
label={_(msg`Cancel`)}
|
|
||||||
size="large"
|
|
||||||
color="secondary_inverted"
|
|
||||||
onPress={() => control.close()}>
|
|
||||||
<ButtonText>
|
|
||||||
<Trans>Cancel</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Admonition type="error">
|
|
||||||
{error.clean || error.raw || _(msg`An unknown error occurred.`)}
|
|
||||||
</Admonition>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import {msg} from '@lingui/core/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import * as SettingsList from '#/screens/Settings/components/SettingsList'
|
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
|
||||||
import {Live_Stroke2_Corner0_Rounded as LiveIcon} from '#/components/icons/Live'
|
|
||||||
import {
|
|
||||||
useLiveEventPreferences,
|
|
||||||
useUpdateLiveEventPreferences,
|
|
||||||
} from '#/features/liveEvents/preferences'
|
|
||||||
|
|
||||||
export function LiveEventFeedsSettingsToggle() {
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {data: prefs} = useLiveEventPreferences()
|
|
||||||
const {
|
|
||||||
isPending,
|
|
||||||
data: updatedPrefs,
|
|
||||||
mutate: update,
|
|
||||||
} = useUpdateLiveEventPreferences({
|
|
||||||
metricContext: 'settings',
|
|
||||||
})
|
|
||||||
const hideAllFeeds = !!(updatedPrefs || prefs)?.hideAllFeeds
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Toggle.Item
|
|
||||||
name="enable_live_event_banner"
|
|
||||||
label={_(msg`Show live events in your Discover Feed`)}
|
|
||||||
value={!hideAllFeeds}
|
|
||||||
onChange={() => {
|
|
||||||
if (!isPending) {
|
|
||||||
update({type: 'toggleHideAllFeeds'})
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<SettingsList.Item>
|
|
||||||
<SettingsList.ItemIcon icon={LiveIcon} />
|
|
||||||
<SettingsList.ItemText>
|
|
||||||
<Trans>Show live events in your Discover Feed</Trans>
|
|
||||||
</SettingsList.ItemText>
|
|
||||||
<Toggle.Platform />
|
|
||||||
</SettingsList.Item>
|
|
||||||
</Toggle.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
import {useMemo} from 'react'
|
|
||||||
|
|
||||||
import {deviceLocales} from '#/locale/deviceLocales'
|
|
||||||
import {useLanguagePrefs} from '#/state/preferences'
|
|
||||||
import {useGeolocation} from '#/geolocation'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* From react-native-localize
|
|
||||||
*
|
|
||||||
* MIT License
|
|
||||||
* Copyright (c) 2017-present, Mathieu Acthernoene
|
|
||||||
*
|
|
||||||
* @see https://github.com/zoontek/react-native-localize/blob/master/LICENSE
|
|
||||||
* @see https://github.com/zoontek/react-native-localize/blob/ee5bf25e0bb8f3b8e4f3fd055f67ad46269c81ea/src/constants.ts
|
|
||||||
*/
|
|
||||||
export const countryCodeToCurrency: Record<string, string> = {
|
|
||||||
ad: 'eur',
|
|
||||||
ae: 'aed',
|
|
||||||
af: 'afn',
|
|
||||||
ag: 'xcd',
|
|
||||||
ai: 'xcd',
|
|
||||||
al: 'all',
|
|
||||||
am: 'amd',
|
|
||||||
an: 'ang',
|
|
||||||
ao: 'aoa',
|
|
||||||
ar: 'ars',
|
|
||||||
as: 'usd',
|
|
||||||
at: 'eur',
|
|
||||||
au: 'aud',
|
|
||||||
aw: 'awg',
|
|
||||||
ax: 'eur',
|
|
||||||
az: 'azn',
|
|
||||||
ba: 'bam',
|
|
||||||
bb: 'bbd',
|
|
||||||
bd: 'bdt',
|
|
||||||
be: 'eur',
|
|
||||||
bf: 'xof',
|
|
||||||
bg: 'bgn',
|
|
||||||
bh: 'bhd',
|
|
||||||
bi: 'bif',
|
|
||||||
bj: 'xof',
|
|
||||||
bl: 'eur',
|
|
||||||
bm: 'bmd',
|
|
||||||
bn: 'bnd',
|
|
||||||
bo: 'bob',
|
|
||||||
bq: 'usd',
|
|
||||||
br: 'brl',
|
|
||||||
bs: 'bsd',
|
|
||||||
bt: 'btn',
|
|
||||||
bv: 'nok',
|
|
||||||
bw: 'bwp',
|
|
||||||
by: 'byn',
|
|
||||||
bz: 'bzd',
|
|
||||||
ca: 'cad',
|
|
||||||
cc: 'aud',
|
|
||||||
cd: 'cdf',
|
|
||||||
cf: 'xaf',
|
|
||||||
cg: 'xaf',
|
|
||||||
ch: 'chf',
|
|
||||||
ci: 'xof',
|
|
||||||
ck: 'nzd',
|
|
||||||
cl: 'clp',
|
|
||||||
cm: 'xaf',
|
|
||||||
cn: 'cny',
|
|
||||||
co: 'cop',
|
|
||||||
cr: 'crc',
|
|
||||||
cu: 'cup',
|
|
||||||
cv: 'cve',
|
|
||||||
cw: 'ang',
|
|
||||||
cx: 'aud',
|
|
||||||
cy: 'eur',
|
|
||||||
cz: 'czk',
|
|
||||||
de: 'eur',
|
|
||||||
dj: 'djf',
|
|
||||||
dk: 'dkk',
|
|
||||||
dm: 'xcd',
|
|
||||||
do: 'dop',
|
|
||||||
dz: 'dzd',
|
|
||||||
ec: 'usd',
|
|
||||||
ee: 'eur',
|
|
||||||
eg: 'egp',
|
|
||||||
eh: 'mad',
|
|
||||||
er: 'ern',
|
|
||||||
es: 'eur',
|
|
||||||
et: 'etb',
|
|
||||||
fi: 'eur',
|
|
||||||
fj: 'fjd',
|
|
||||||
fk: 'fkp',
|
|
||||||
fm: 'usd',
|
|
||||||
fo: 'dkk',
|
|
||||||
fr: 'eur',
|
|
||||||
ga: 'xaf',
|
|
||||||
gb: 'gbp',
|
|
||||||
gd: 'xcd',
|
|
||||||
ge: 'gel',
|
|
||||||
gf: 'eur',
|
|
||||||
gg: 'gbp',
|
|
||||||
gh: 'ghs',
|
|
||||||
gi: 'gip',
|
|
||||||
gl: 'dkk',
|
|
||||||
gm: 'gmd',
|
|
||||||
gn: 'gnf',
|
|
||||||
gp: 'eur',
|
|
||||||
gq: 'xaf',
|
|
||||||
gr: 'eur',
|
|
||||||
gs: 'gbp',
|
|
||||||
gt: 'gtq',
|
|
||||||
gu: 'usd',
|
|
||||||
gw: 'xof',
|
|
||||||
gy: 'gyd',
|
|
||||||
hk: 'hkd',
|
|
||||||
hm: 'aud',
|
|
||||||
hn: 'hnl',
|
|
||||||
hr: 'hrk',
|
|
||||||
ht: 'htg',
|
|
||||||
hu: 'huf',
|
|
||||||
id: 'idr',
|
|
||||||
ie: 'eur',
|
|
||||||
il: 'ils',
|
|
||||||
im: 'gbp',
|
|
||||||
in: 'inr',
|
|
||||||
io: 'usd',
|
|
||||||
iq: 'iqd',
|
|
||||||
ir: 'irr',
|
|
||||||
is: 'isk',
|
|
||||||
it: 'eur',
|
|
||||||
je: 'gbp',
|
|
||||||
jm: 'jmd',
|
|
||||||
jo: 'jod',
|
|
||||||
jp: 'jpy',
|
|
||||||
ke: 'kes',
|
|
||||||
kg: 'kgs',
|
|
||||||
kh: 'khr',
|
|
||||||
ki: 'aud',
|
|
||||||
km: 'kmf',
|
|
||||||
kn: 'xcd',
|
|
||||||
kp: 'kpw',
|
|
||||||
kr: 'krw',
|
|
||||||
kw: 'kwd',
|
|
||||||
ky: 'kyd',
|
|
||||||
kz: 'kzt',
|
|
||||||
la: 'lak',
|
|
||||||
lb: 'lbp',
|
|
||||||
lc: 'xcd',
|
|
||||||
li: 'chf',
|
|
||||||
lk: 'lkr',
|
|
||||||
lr: 'lrd',
|
|
||||||
ls: 'lsl',
|
|
||||||
lt: 'eur',
|
|
||||||
lu: 'eur',
|
|
||||||
lv: 'eur',
|
|
||||||
ly: 'lyd',
|
|
||||||
ma: 'mad',
|
|
||||||
mc: 'eur',
|
|
||||||
md: 'mdl',
|
|
||||||
me: 'eur',
|
|
||||||
mf: 'eur',
|
|
||||||
mg: 'mga',
|
|
||||||
mh: 'usd',
|
|
||||||
mk: 'mkd',
|
|
||||||
ml: 'xof',
|
|
||||||
mm: 'mmk',
|
|
||||||
mn: 'mnt',
|
|
||||||
mo: 'mop',
|
|
||||||
mp: 'usd',
|
|
||||||
mq: 'eur',
|
|
||||||
mr: 'mro',
|
|
||||||
ms: 'xcd',
|
|
||||||
mt: 'eur',
|
|
||||||
mu: 'mur',
|
|
||||||
mv: 'mvr',
|
|
||||||
mw: 'mwk',
|
|
||||||
mx: 'mxn',
|
|
||||||
my: 'myr',
|
|
||||||
mz: 'mzn',
|
|
||||||
na: 'nad',
|
|
||||||
nc: 'xpf',
|
|
||||||
ne: 'xof',
|
|
||||||
nf: 'aud',
|
|
||||||
ng: 'ngn',
|
|
||||||
ni: 'nio',
|
|
||||||
nl: 'eur',
|
|
||||||
no: 'nok',
|
|
||||||
np: 'npr',
|
|
||||||
nr: 'aud',
|
|
||||||
nu: 'nzd',
|
|
||||||
nz: 'nzd',
|
|
||||||
om: 'omr',
|
|
||||||
pa: 'pab',
|
|
||||||
pe: 'pen',
|
|
||||||
pf: 'xpf',
|
|
||||||
pg: 'pgk',
|
|
||||||
ph: 'php',
|
|
||||||
pk: 'pkr',
|
|
||||||
pl: 'pln',
|
|
||||||
pm: 'eur',
|
|
||||||
pn: 'nzd',
|
|
||||||
pr: 'usd',
|
|
||||||
ps: 'ils',
|
|
||||||
pt: 'eur',
|
|
||||||
pw: 'usd',
|
|
||||||
py: 'pyg',
|
|
||||||
qa: 'qar',
|
|
||||||
re: 'eur',
|
|
||||||
ro: 'ron',
|
|
||||||
rs: 'rsd',
|
|
||||||
ru: 'rub',
|
|
||||||
rw: 'rwf',
|
|
||||||
sa: 'sar',
|
|
||||||
sb: 'sbd',
|
|
||||||
sc: 'scr',
|
|
||||||
sd: 'sdg',
|
|
||||||
se: 'sek',
|
|
||||||
sg: 'sgd',
|
|
||||||
sh: 'shp',
|
|
||||||
si: 'eur',
|
|
||||||
sj: 'nok',
|
|
||||||
sk: 'eur',
|
|
||||||
sl: 'sll',
|
|
||||||
sm: 'eur',
|
|
||||||
sn: 'xof',
|
|
||||||
so: 'sos',
|
|
||||||
sr: 'srd',
|
|
||||||
ss: 'ssp',
|
|
||||||
st: 'std',
|
|
||||||
sv: 'svc',
|
|
||||||
sx: 'ang',
|
|
||||||
sy: 'syp',
|
|
||||||
sz: 'szl',
|
|
||||||
tc: 'usd',
|
|
||||||
td: 'xaf',
|
|
||||||
tf: 'eur',
|
|
||||||
tg: 'xof',
|
|
||||||
th: 'thb',
|
|
||||||
tj: 'tjs',
|
|
||||||
tk: 'nzd',
|
|
||||||
tl: 'usd',
|
|
||||||
tm: 'tmt',
|
|
||||||
tn: 'tnd',
|
|
||||||
to: 'top',
|
|
||||||
tr: 'try',
|
|
||||||
tt: 'ttd',
|
|
||||||
tv: 'aud',
|
|
||||||
tw: 'twd',
|
|
||||||
tz: 'tzs',
|
|
||||||
ua: 'uah',
|
|
||||||
ug: 'ugx',
|
|
||||||
um: 'usd',
|
|
||||||
us: 'usd',
|
|
||||||
uy: 'uyu',
|
|
||||||
uz: 'uzs',
|
|
||||||
va: 'eur',
|
|
||||||
vc: 'xcd',
|
|
||||||
ve: 'vef',
|
|
||||||
vg: 'usd',
|
|
||||||
vi: 'usd',
|
|
||||||
vn: 'vnd',
|
|
||||||
vu: 'vuv',
|
|
||||||
wf: 'xpf',
|
|
||||||
ws: 'wst',
|
|
||||||
ye: 'yer',
|
|
||||||
yt: 'eur',
|
|
||||||
za: 'zar',
|
|
||||||
zm: 'zmw',
|
|
||||||
zw: 'zwl',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Best-guess currency formatting.
|
|
||||||
*
|
|
||||||
* Attempts to use `getLocales` from `expo-localization` if available,
|
|
||||||
* otherwise falls back to the `persisted.appLanguage` setting, and geolocation
|
|
||||||
* API for region.
|
|
||||||
*/
|
|
||||||
export function useFormatCurrency(
|
|
||||||
options?: Parameters<typeof Intl.NumberFormat>[1],
|
|
||||||
) {
|
|
||||||
const geolocation = useGeolocation()
|
|
||||||
const {appLanguage} = useLanguagePrefs()
|
|
||||||
return useMemo(() => {
|
|
||||||
const locale = deviceLocales.at(0)
|
|
||||||
const languageTag = locale?.languageTag || appLanguage || 'en-US'
|
|
||||||
const countryCode = (
|
|
||||||
locale?.regionCode ||
|
|
||||||
geolocation?.countryCode ||
|
|
||||||
'us'
|
|
||||||
).toLowerCase()
|
|
||||||
const currency = countryCodeToCurrency[countryCode] || 'usd'
|
|
||||||
const format = new Intl.NumberFormat(languageTag, {
|
|
||||||
...(options || {}),
|
|
||||||
style: 'currency',
|
|
||||||
currency: currency,
|
|
||||||
}).format
|
|
||||||
|
|
||||||
return {
|
|
||||||
format,
|
|
||||||
currency,
|
|
||||||
countryCode,
|
|
||||||
languageTag,
|
|
||||||
}
|
|
||||||
}, [geolocation, appLanguage, options])
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import {withDelay, withSequence, withTiming} from 'react-native-reanimated'
|
|
||||||
|
|
||||||
export function ShrinkAndPop() {
|
|
||||||
'worklet'
|
|
||||||
|
|
||||||
const animations = {
|
|
||||||
opacity: withDelay(125, withTiming(0, {duration: 125})),
|
|
||||||
transform: [
|
|
||||||
{
|
|
||||||
scale: withSequence(
|
|
||||||
withTiming(0.7, {duration: 75}),
|
|
||||||
withTiming(1.1, {duration: 150}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialValues = {
|
|
||||||
opacity: 1,
|
|
||||||
transform: [{scale: 1}],
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
animations,
|
|
||||||
initialValues,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import {useEffect, useState} from 'react'
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
|
||||||
|
|
||||||
import {getTabState, TabState} from '#/lib/routes/helpers'
|
|
||||||
|
|
||||||
export function useTabFocusEffect(
|
|
||||||
tabName: string,
|
|
||||||
cb: (isInside: boolean) => void,
|
|
||||||
) {
|
|
||||||
const [isInside, setIsInside] = useState(false)
|
|
||||||
|
|
||||||
// get root navigator state
|
|
||||||
let nav = useNavigation()
|
|
||||||
while (nav.getParent()) {
|
|
||||||
nav = nav.getParent()
|
|
||||||
}
|
|
||||||
const state = nav.getState()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// check if inside
|
|
||||||
let v = getTabState(state, tabName) !== TabState.Outside
|
|
||||||
if (v !== isInside) {
|
|
||||||
// fire
|
|
||||||
setIsInside(v)
|
|
||||||
cb(v)
|
|
||||||
}
|
|
||||||
}, [state, isInside, setIsInside, tabName, cb])
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import {useCallback, useEffect, useRef} from 'react'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper hook to run persistent timers on views
|
|
||||||
*/
|
|
||||||
export function useTimer(time: number, handler: () => void) {
|
|
||||||
const timer = useRef<undefined | NodeJS.Timeout>(undefined)
|
|
||||||
|
|
||||||
// function to restart the timer
|
|
||||||
const reset = useCallback(() => {
|
|
||||||
if (timer.current) {
|
|
||||||
clearTimeout(timer.current)
|
|
||||||
}
|
|
||||||
timer.current = setTimeout(handler, time)
|
|
||||||
}, [time, timer, handler])
|
|
||||||
|
|
||||||
// function to cancel the timer
|
|
||||||
const cancel = useCallback(() => {
|
|
||||||
if (timer.current) {
|
|
||||||
clearTimeout(timer.current)
|
|
||||||
timer.current = undefined
|
|
||||||
}
|
|
||||||
}, [timer])
|
|
||||||
|
|
||||||
// start the timer immediately
|
|
||||||
useEffect(() => {
|
|
||||||
reset()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return [reset, cancel]
|
|
||||||
}
|
|
||||||
@@ -14,13 +14,13 @@ import {
|
|||||||
useNavigation,
|
useNavigation,
|
||||||
} from '@react-navigation/native'
|
} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useCleanError} from '#/lib/hooks/useCleanError'
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
|
||||||
import {
|
import {
|
||||||
type CommonNavigatorParams,
|
type CommonNavigatorParams,
|
||||||
type NativeStackScreenProps,
|
type NativeStackScreenProps,
|
||||||
} from '#/lib/routes/types'
|
} from '#/lib/routes/types'
|
||||||
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
|
import {useBookmarkMutation} from '#/state/queries/bookmarks/useBookmarkMutation'
|
||||||
import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
|
import {useBookmarksQuery} from '#/state/queries/bookmarks/useBookmarksQuery'
|
||||||
import {Post} from '#/view/com/post/Post'
|
import {Post} from '#/view/com/post/Post'
|
||||||
@@ -92,7 +92,6 @@ type ListItem =
|
|||||||
|
|
||||||
function BookmarksInner() {
|
function BookmarksInner() {
|
||||||
const initialNumToRender = useInitialNumToRender()
|
const initialNumToRender = useInitialNumToRender()
|
||||||
const cleanError = useCleanError()
|
|
||||||
const [isPTRing, setIsPTRing] = useState(false)
|
const [isPTRing, setIsPTRing] = useState(false)
|
||||||
const trackPostView = usePostViewTracking('Bookmarks')
|
const trackPostView = usePostViewTracking('Bookmarks')
|
||||||
const {
|
const {
|
||||||
@@ -104,10 +103,6 @@ function BookmarksInner() {
|
|||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
} = useBookmarksQuery()
|
} = useBookmarksQuery()
|
||||||
const cleanedError = useMemo(() => {
|
|
||||||
const {raw, clean} = cleanError(error)
|
|
||||||
return clean || raw
|
|
||||||
}, [error, cleanError])
|
|
||||||
|
|
||||||
const onRefresh = useCallback(async () => {
|
const onRefresh = useCallback(async () => {
|
||||||
setIsPTRing(true)
|
setIsPTRing(true)
|
||||||
@@ -174,10 +169,10 @@ function BookmarksInner() {
|
|||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={() => void onRefresh()}
|
||||||
onEndReached={onEndReached}
|
onEndReached={() => void onEndReached()}
|
||||||
onEndReachedThreshold={4}
|
onEndReachedThreshold={4}
|
||||||
onItemSeen={item => {
|
onItemSeen={(item: ListItem) => {
|
||||||
if (item.type === 'bookmark') {
|
if (item.type === 'bookmark') {
|
||||||
trackPostView(item.bookmark.item)
|
trackPostView(item.bookmark.item)
|
||||||
}
|
}
|
||||||
@@ -185,7 +180,7 @@ function BookmarksInner() {
|
|||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<ListFooter
|
<ListFooter
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
error={cleanedError}
|
error={cleanError(error)}
|
||||||
onRetry={fetchNextPage}
|
onRetry={fetchNextPage}
|
||||||
style={[isEmpty && a.border_t_0]}
|
style={[isEmpty && a.border_t_0]}
|
||||||
/>
|
/>
|
||||||
@@ -209,7 +204,6 @@ function BookmarkNotFound({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {mutateAsync: bookmark} = useBookmarkMutation()
|
const {mutateAsync: bookmark} = useBookmarkMutation()
|
||||||
const cleanError = useCleanError()
|
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -217,9 +211,8 @@ function BookmarkNotFound({
|
|||||||
toast.show(_(msg`Removed from saved posts`), {
|
toast.show(_(msg`Removed from saved posts`), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (err) {
|
||||||
const {raw, clean} = cleanError(e)
|
toast.show(cleanError(err), {
|
||||||
toast.show(clean || raw || e, {
|
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -259,7 +252,7 @@ function BookmarkNotFound({
|
|||||||
label={_(msg`Remove from saved posts`)}
|
label={_(msg`Remove from saved posts`)}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onPress={remove}>
|
onPress={() => void remove()}>
|
||||||
<ButtonIcon icon={BookmarkFilled} />
|
<ButtonIcon icon={BookmarkFilled} />
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Remove</Trans>
|
<Trans>Remove</Trans>
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import {View} from 'react-native'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans} from '@lingui/react/macro'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {ButtonText} from '#/components/Button'
|
|
||||||
import {BookmarkDeleteLarge} from '#/components/icons/Bookmark'
|
|
||||||
import {Link} from '#/components/Link'
|
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
|
|
||||||
export function EmptyState() {
|
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.align_center,
|
|
||||||
{
|
|
||||||
paddingVertical: 64,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<BookmarkDeleteLarge
|
|
||||||
width={64}
|
|
||||||
fill={t.atoms.text_contrast_medium.color}
|
|
||||||
/>
|
|
||||||
<View style={[a.pt_sm]}>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.text_lg,
|
|
||||||
a.font_medium,
|
|
||||||
a.text_center,
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
]}>
|
|
||||||
<Trans>Nothing saved yet</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={[a.pt_2xl]}>
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
action="navigate"
|
|
||||||
label={_(
|
|
||||||
msg({
|
|
||||||
message: `Go home`,
|
|
||||||
context: `Button to go back to the home timeline`,
|
|
||||||
}),
|
|
||||||
)}
|
|
||||||
size="small"
|
|
||||||
color="secondary">
|
|
||||||
<ButtonText>
|
|
||||||
<Trans context="Button to go back to the home timeline">
|
|
||||||
Go home
|
|
||||||
</Trans>
|
|
||||||
</ButtonText>
|
|
||||||
</Link>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import {type AppBskyFeedGetSuggestedFeeds} from '@atproto/api'
|
|
||||||
import {
|
|
||||||
type InfiniteData,
|
|
||||||
type QueryKey,
|
|
||||||
useInfiniteQuery,
|
|
||||||
} from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import {STALE} from '#/state/queries'
|
|
||||||
import {useAgent} from '#/state/session'
|
|
||||||
|
|
||||||
const suggestedFeedsQueryKeyRoot = 'suggestedFeeds'
|
|
||||||
export const suggestedFeedsQueryKey = [suggestedFeedsQueryKeyRoot]
|
|
||||||
|
|
||||||
export function useSuggestedFeedsQuery() {
|
|
||||||
const agent = useAgent()
|
|
||||||
return useInfiniteQuery<
|
|
||||||
AppBskyFeedGetSuggestedFeeds.OutputSchema,
|
|
||||||
Error,
|
|
||||||
InfiniteData<AppBskyFeedGetSuggestedFeeds.OutputSchema>,
|
|
||||||
QueryKey,
|
|
||||||
string | undefined
|
|
||||||
>({
|
|
||||||
staleTime: STALE.HOURS.ONE,
|
|
||||||
queryKey: suggestedFeedsQueryKey,
|
|
||||||
queryFn: async ({pageParam}) => {
|
|
||||||
const res = await agent.app.bsky.feed.getSuggestedFeeds({
|
|
||||||
limit: 10,
|
|
||||||
cursor: pageParam,
|
|
||||||
})
|
|
||||||
return res.data
|
|
||||||
},
|
|
||||||
initialPageParam: undefined,
|
|
||||||
getNextPageParam: lastPage => lastPage.cursor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import {createContext, useContext} from 'react'
|
|
||||||
|
|
||||||
interface PostProgressState {
|
|
||||||
progress: number
|
|
||||||
status: 'pending' | 'success' | 'error' | 'idle'
|
|
||||||
error?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostProgressContext = createContext<PostProgressState>({
|
|
||||||
progress: 0,
|
|
||||||
status: 'idle',
|
|
||||||
})
|
|
||||||
PostProgressContext.displayName = 'PostProgressContext'
|
|
||||||
|
|
||||||
export function Provider() {}
|
|
||||||
|
|
||||||
export function usePostProgress() {
|
|
||||||
return useContext(PostProgressContext)
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import {createContext, useContext, useState} from 'react'
|
|
||||||
|
|
||||||
type StateContext =
|
|
||||||
| {
|
|
||||||
uri: string
|
|
||||||
isClip?: boolean
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
type SetContext = (v: StateContext) => void
|
|
||||||
|
|
||||||
const stateContext = createContext<StateContext>(undefined)
|
|
||||||
stateContext.displayName = 'ActiveStarterPackStateContext'
|
|
||||||
const setContext = createContext<SetContext>((_: StateContext) => {})
|
|
||||||
setContext.displayName = 'ActiveStarterPackSetContext'
|
|
||||||
|
|
||||||
export function Provider({children}: {children: React.ReactNode}) {
|
|
||||||
const [state, setState] = useState<StateContext>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<stateContext.Provider value={state}>
|
|
||||||
<setContext.Provider value={setState}>{children}</setContext.Provider>
|
|
||||||
</stateContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useActiveStarterPack = () => useContext(stateContext)
|
|
||||||
export const useSetActiveStarterPack = () => useContext(setContext)
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import {View} from 'react-native'
|
|
||||||
import {KeyboardStickyView} from 'react-native-keyboard-controller'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {IS_WEB} from '#/env'
|
|
||||||
|
|
||||||
export function KeyboardAccessory({children}: {children: React.ReactNode}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const {bottom} = useSafeAreaInsets()
|
|
||||||
|
|
||||||
const style = [
|
|
||||||
a.flex_row,
|
|
||||||
a.py_xs,
|
|
||||||
a.pl_sm,
|
|
||||||
a.pr_xl,
|
|
||||||
a.align_center,
|
|
||||||
a.border_t,
|
|
||||||
t.atoms.border_contrast_medium,
|
|
||||||
t.atoms.bg,
|
|
||||||
]
|
|
||||||
|
|
||||||
// todo: when iPad support is added, it should also not use the KeyboardStickyView
|
|
||||||
if (IS_WEB) {
|
|
||||||
return <View style={style}>{children}</View>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KeyboardStickyView offset={{closed: -bottom}} style={style}>
|
|
||||||
{children}
|
|
||||||
</KeyboardStickyView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
import {
|
|
||||||
forwardRef,
|
|
||||||
type JSX,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import {
|
|
||||||
type NativeScrollEvent,
|
|
||||||
type NativeSyntheticEvent,
|
|
||||||
Pressable,
|
|
||||||
RefreshControl,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
|
|
||||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {clamp} from '#/lib/numbers'
|
|
||||||
import {colors, s} from '#/lib/styles'
|
|
||||||
import {IS_ANDROID} from '#/env'
|
|
||||||
import {Text} from './text/Text'
|
|
||||||
import {FlatList_INTERNAL} from './Views'
|
|
||||||
|
|
||||||
const HEADER_ITEM = {_reactKey: '__header__'}
|
|
||||||
const SELECTOR_ITEM = {_reactKey: '__selector__'}
|
|
||||||
const STICKY_HEADER_INDICES = [1]
|
|
||||||
|
|
||||||
export type ViewSelectorHandle = {
|
|
||||||
scrollToTop: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ViewSelector = forwardRef<
|
|
||||||
ViewSelectorHandle,
|
|
||||||
{
|
|
||||||
sections: string[]
|
|
||||||
items: any[]
|
|
||||||
refreshing?: boolean
|
|
||||||
swipeEnabled?: boolean
|
|
||||||
renderHeader?: () => JSX.Element
|
|
||||||
renderItem: (item: any) => JSX.Element
|
|
||||||
ListFooterComponent?:
|
|
||||||
| React.ComponentType<any>
|
|
||||||
| React.ReactElement<any>
|
|
||||||
| null
|
|
||||||
| undefined
|
|
||||||
onSelectView?: (viewIndex: number) => void
|
|
||||||
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
|
|
||||||
onRefresh?: () => void
|
|
||||||
onEndReached?: (info: {distanceFromEnd: number}) => void
|
|
||||||
}
|
|
||||||
>(function ViewSelectorImpl(
|
|
||||||
{
|
|
||||||
sections,
|
|
||||||
items,
|
|
||||||
refreshing,
|
|
||||||
renderHeader,
|
|
||||||
renderItem,
|
|
||||||
ListFooterComponent,
|
|
||||||
onSelectView,
|
|
||||||
onScroll,
|
|
||||||
onRefresh,
|
|
||||||
onEndReached,
|
|
||||||
},
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
|
||||||
const flatListRef = useRef<FlatList_INTERNAL>(null)
|
|
||||||
|
|
||||||
// events
|
|
||||||
// =
|
|
||||||
|
|
||||||
const keyExtractor = useCallback((item: any) => item._reactKey, [])
|
|
||||||
|
|
||||||
const onPressSelection = useCallback(
|
|
||||||
(index: number) => setSelectedIndex(clamp(index, 0, sections.length)),
|
|
||||||
[setSelectedIndex, sections],
|
|
||||||
)
|
|
||||||
useEffect(() => {
|
|
||||||
onSelectView?.(selectedIndex)
|
|
||||||
}, [selectedIndex, onSelectView])
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
scrollToTop: () => {
|
|
||||||
flatListRef.current?.scrollToOffset({offset: 0})
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
// rendering
|
|
||||||
// =
|
|
||||||
|
|
||||||
const renderItemInternal = useCallback(
|
|
||||||
({item}: {item: any}) => {
|
|
||||||
if (item === HEADER_ITEM) {
|
|
||||||
if (renderHeader) {
|
|
||||||
return renderHeader()
|
|
||||||
}
|
|
||||||
return <View />
|
|
||||||
} else if (item === SELECTOR_ITEM) {
|
|
||||||
return (
|
|
||||||
<Selector
|
|
||||||
items={sections}
|
|
||||||
selectedIndex={selectedIndex}
|
|
||||||
onSelect={onPressSelection}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return renderItem(item)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[sections, selectedIndex, onPressSelection, renderHeader, renderItem],
|
|
||||||
)
|
|
||||||
|
|
||||||
const data = useMemo(() => [HEADER_ITEM, SELECTOR_ITEM, ...items], [items])
|
|
||||||
return (
|
|
||||||
<FlatList_INTERNAL
|
|
||||||
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
|
|
||||||
ref={flatListRef}
|
|
||||||
data={data}
|
|
||||||
keyExtractor={keyExtractor}
|
|
||||||
renderItem={renderItemInternal}
|
|
||||||
ListFooterComponent={ListFooterComponent}
|
|
||||||
// NOTE sticky header disabled on android due to major performance issues -prf
|
|
||||||
stickyHeaderIndices={IS_ANDROID ? undefined : STICKY_HEADER_INDICES}
|
|
||||||
onScroll={onScroll}
|
|
||||||
onEndReached={onEndReached}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl
|
|
||||||
refreshing={refreshing!}
|
|
||||||
onRefresh={onRefresh}
|
|
||||||
tintColor={pal.colors.text}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
onEndReachedThreshold={0.6}
|
|
||||||
contentContainerStyle={s.contentContainer}
|
|
||||||
removeClippedSubviews={true}
|
|
||||||
scrollIndicatorInsets={{right: 1}} // fixes a bug where the scroll indicator is on the middle of the screen https://github.com/bluesky-social/social-app/pull/464
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export function Selector({
|
|
||||||
selectedIndex,
|
|
||||||
items,
|
|
||||||
onSelect,
|
|
||||||
}: {
|
|
||||||
selectedIndex: number
|
|
||||||
items: string[]
|
|
||||||
onSelect?: (index: number) => void
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const borderColor = useColorSchemeStyle(
|
|
||||||
{borderColor: colors.black},
|
|
||||||
{borderColor: colors.white},
|
|
||||||
)
|
|
||||||
|
|
||||||
const onPressItem = (index: number) => {
|
|
||||||
onSelect?.(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: pal.colors.background,
|
|
||||||
}}>
|
|
||||||
<ScrollView
|
|
||||||
testID="selector"
|
|
||||||
horizontal
|
|
||||||
showsHorizontalScrollIndicator={false}>
|
|
||||||
<View style={[pal.view, styles.outer]}>
|
|
||||||
{items.map((item, i) => {
|
|
||||||
const selected = i === selectedIndex
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
testID={`selector-${i}`}
|
|
||||||
key={item}
|
|
||||||
onPress={() => onPressItem(i)}
|
|
||||||
accessibilityLabel={item}
|
|
||||||
accessibilityHint={`Selects ${item}`}
|
|
||||||
// TODO: Modify the component API such that lint fails
|
|
||||||
// at the invocation site as well
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.item,
|
|
||||||
selected && styles.itemSelected,
|
|
||||||
borderColor,
|
|
||||||
]}>
|
|
||||||
<Text
|
|
||||||
style={
|
|
||||||
selected
|
|
||||||
? [styles.labelSelected, pal.text]
|
|
||||||
: [styles.label, pal.textLight]
|
|
||||||
}>
|
|
||||||
{item}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</View>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
outer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
paddingHorizontal: 14,
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
marginRight: 14,
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
paddingTop: 8,
|
|
||||||
paddingBottom: 12,
|
|
||||||
},
|
|
||||||
itemSelected: {
|
|
||||||
borderBottomWidth: 3,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
labelSelected: {
|
|
||||||
fontWeight: '600',
|
|
||||||
},
|
|
||||||
underline: {
|
|
||||||
position: 'absolute',
|
|
||||||
height: 4,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
import {useState} from 'react'
|
|
||||||
import {ScrollView, View} from 'react-native'
|
|
||||||
import {msg} from '@lingui/core/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {
|
|
||||||
type CommonNavigatorParams,
|
|
||||||
type NativeStackScreenProps,
|
|
||||||
} from '#/lib/routes/types'
|
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {type PaletteColorName, ThemeProvider} from '#/lib/ThemeContext'
|
|
||||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
|
||||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
|
||||||
import * as LoadingPlaceholder from '#/view/com/util/LoadingPlaceholder'
|
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {ViewSelector} from '#/view/com/util/ViewSelector'
|
|
||||||
import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag'
|
|
||||||
import * as Layout from '#/components/Layout'
|
|
||||||
import * as Toast from '#/components/Toast'
|
|
||||||
|
|
||||||
const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
|
|
||||||
|
|
||||||
export const DebugScreen = ({}: NativeStackScreenProps<
|
|
||||||
CommonNavigatorParams,
|
|
||||||
'Debug'
|
|
||||||
>) => {
|
|
||||||
const [colorScheme, setColorScheme] = useState<'light' | 'dark'>('light')
|
|
||||||
const onToggleColorScheme = () => {
|
|
||||||
setColorScheme(colorScheme === 'light' ? 'dark' : 'light')
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ThemeProvider theme={colorScheme}>
|
|
||||||
<Layout.Screen>
|
|
||||||
<DebugInner
|
|
||||||
colorScheme={colorScheme}
|
|
||||||
onToggleColorScheme={onToggleColorScheme}
|
|
||||||
/>
|
|
||||||
</Layout.Screen>
|
|
||||||
</ThemeProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DebugInner({}: {
|
|
||||||
colorScheme: 'light' | 'dark'
|
|
||||||
onToggleColorScheme: () => void
|
|
||||||
}) {
|
|
||||||
const [currentView, setCurrentView] = useState<number>(0)
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
const renderItem = (item: any) => {
|
|
||||||
return (
|
|
||||||
<View key={`view-${item.currentView}`}>
|
|
||||||
{item.currentView === 3 ? (
|
|
||||||
<NotifsView />
|
|
||||||
) : item.currentView === 2 ? (
|
|
||||||
<ErrorView />
|
|
||||||
) : item.currentView === 1 ? (
|
|
||||||
<ControlsView />
|
|
||||||
) : (
|
|
||||||
<BaseView />
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = [{currentView}]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[s.hContentRegion, pal.view]}>
|
|
||||||
<ViewHeader title={_(msg`Debug panel`)} />
|
|
||||||
<ViewSelector
|
|
||||||
swipeEnabled
|
|
||||||
sections={MAIN_VIEWS}
|
|
||||||
items={items}
|
|
||||||
renderItem={renderItem}
|
|
||||||
onSelectView={setCurrentView}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Heading({label}: {label: string}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
return (
|
|
||||||
<View style={[s.pt10, s.pb5]}>
|
|
||||||
<Text type="title-lg" style={pal.text}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function BaseView() {
|
|
||||||
return (
|
|
||||||
<View style={[s.pl10, s.pr10]}>
|
|
||||||
<Heading label="Typography" />
|
|
||||||
<TypographyView />
|
|
||||||
<Heading label="Palettes" />
|
|
||||||
<PaletteView palette="default" />
|
|
||||||
<PaletteView palette="primary" />
|
|
||||||
<PaletteView palette="secondary" />
|
|
||||||
<PaletteView palette="inverted" />
|
|
||||||
<PaletteView palette="error" />
|
|
||||||
<Heading label="Empty state" />
|
|
||||||
<EmptyStateView />
|
|
||||||
<Heading label="Loading placeholders" />
|
|
||||||
<LoadingPlaceholderView />
|
|
||||||
<View style={s.footerSpacer} />
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ControlsView() {
|
|
||||||
return (
|
|
||||||
<ScrollView style={[s.pl10, s.pr10]}>
|
|
||||||
<Heading label="Buttons" />
|
|
||||||
<ButtonsView />
|
|
||||||
<View style={s.footerSpacer} />
|
|
||||||
</ScrollView>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ErrorView() {
|
|
||||||
return (
|
|
||||||
<View style={s.p10}>
|
|
||||||
<View style={s.mb5}>
|
|
||||||
<ErrorScreen
|
|
||||||
title="Error screen"
|
|
||||||
message="A major error occurred that led the entire screen to fail"
|
|
||||||
details="Here are some details"
|
|
||||||
onPressTryAgain={() => {}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={s.mb5}>
|
|
||||||
<ErrorMessage message="This is an error that occurred while things were being done" />
|
|
||||||
</View>
|
|
||||||
<View style={s.mb5}>
|
|
||||||
<ErrorMessage
|
|
||||||
message="This is an error that occurred while things were being done"
|
|
||||||
numberOfLines={1}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={s.mb5}>
|
|
||||||
<ErrorMessage
|
|
||||||
message="This is an error that occurred while things were being done"
|
|
||||||
onPressTryAgain={() => {}}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={s.mb5}>
|
|
||||||
<ErrorMessage
|
|
||||||
message="This is an error that occurred while things were being done"
|
|
||||||
onPressTryAgain={() => {}}
|
|
||||||
numberOfLines={1}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NotifsView() {
|
|
||||||
const triggerPush = () => {
|
|
||||||
// TODO: implement local notification for testing
|
|
||||||
}
|
|
||||||
const triggerToast = () => {
|
|
||||||
Toast.show('The task has been completed')
|
|
||||||
}
|
|
||||||
const triggerToast2 = () => {
|
|
||||||
Toast.show('The task has been completed successfully and with no problems')
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={s.p10}>
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button onPress={triggerPush} label="Trigger Push" />
|
|
||||||
<Button onPress={triggerToast} label="Trigger Toast" />
|
|
||||||
<Button onPress={triggerToast2} label="Trigger Toast 2" />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaletteView({palette}: {palette: PaletteColorName}) {
|
|
||||||
const defaultPal = usePalette('default')
|
|
||||||
const pal = usePalette(palette)
|
|
||||||
return (
|
|
||||||
<View style={[pal.view, pal.border, s.p10, s.mb5, {borderWidth: 1}]}>
|
|
||||||
<Text style={[pal.text]}>{palette} colors</Text>
|
|
||||||
<Text style={[pal.textLight]}>Light text</Text>
|
|
||||||
<Text style={[pal.link]}>Link text</Text>
|
|
||||||
{palette !== 'default' && (
|
|
||||||
<View style={[defaultPal.view]}>
|
|
||||||
<Text style={[pal.textInverted]}>Inverted text</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TypographyView() {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
return (
|
|
||||||
<View style={[pal.view]}>
|
|
||||||
<Text type="2xl-thin" style={[pal.text]}>
|
|
||||||
'2xl-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="2xl" style={[pal.text]}>
|
|
||||||
'2xl' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="2xl-medium" style={[pal.text]}>
|
|
||||||
'2xl-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="2xl-bold" style={[pal.text]}>
|
|
||||||
'2xl-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="2xl-heavy" style={[pal.text]}>
|
|
||||||
'2xl-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xl-thin" style={[pal.text]}>
|
|
||||||
'xl-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xl" style={[pal.text]}>
|
|
||||||
'xl' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xl-medium" style={[pal.text]}>
|
|
||||||
'xl-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xl-bold" style={[pal.text]}>
|
|
||||||
'xl-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xl-heavy" style={[pal.text]}>
|
|
||||||
'xl-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text]}>
|
|
||||||
'lg-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="lg" style={[pal.text]}>
|
|
||||||
'lg' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-medium" style={[pal.text]}>
|
|
||||||
'lg-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-bold" style={[pal.text]}>
|
|
||||||
'lg-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-heavy" style={[pal.text]}>
|
|
||||||
'lg-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="md-thin" style={[pal.text]}>
|
|
||||||
'md-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="md" style={[pal.text]}>
|
|
||||||
'md' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="md-medium" style={[pal.text]}>
|
|
||||||
'md-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="md-bold" style={[pal.text]}>
|
|
||||||
'md-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="md-heavy" style={[pal.text]}>
|
|
||||||
'md-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="sm-thin" style={[pal.text]}>
|
|
||||||
'sm-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="sm" style={[pal.text]}>
|
|
||||||
'sm' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="sm-medium" style={[pal.text]}>
|
|
||||||
'sm-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="sm-bold" style={[pal.text]}>
|
|
||||||
'sm-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="sm-heavy" style={[pal.text]}>
|
|
||||||
'sm-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xs-thin" style={[pal.text]}>
|
|
||||||
'xs-thin' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xs" style={[pal.text]}>
|
|
||||||
'xs' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xs-medium" style={[pal.text]}>
|
|
||||||
'xs-medium' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xs-bold" style={[pal.text]}>
|
|
||||||
'xs-bold' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="xs-heavy" style={[pal.text]}>
|
|
||||||
'xs-heavy' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text type="title-2xl" style={[pal.text]}>
|
|
||||||
'title-2xl' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="title-xl" style={[pal.text]}>
|
|
||||||
'title-xl' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="title-lg" style={[pal.text]}>
|
|
||||||
'title-lg' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="title" style={[pal.text]}>
|
|
||||||
'title' lorem ipsum dolor
|
|
||||||
</Text>
|
|
||||||
<Text type="button" style={[pal.text]}>
|
|
||||||
Button
|
|
||||||
</Text>
|
|
||||||
<Text type="button-lg" style={[pal.text]}>
|
|
||||||
Button-lg
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function EmptyStateView() {
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EmptyState
|
|
||||||
icon={HashtagWideIcon}
|
|
||||||
iconSize="2xl"
|
|
||||||
message={_(msg`This is an empty state`)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LoadingPlaceholderView() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LoadingPlaceholder.PostLoadingPlaceholder />
|
|
||||||
<LoadingPlaceholder.NotificationLoadingPlaceholder />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ButtonsView() {
|
|
||||||
const defaultPal = usePalette('default')
|
|
||||||
const buttonStyles = {marginRight: 5}
|
|
||||||
return (
|
|
||||||
<View style={[defaultPal.view]}>
|
|
||||||
<View style={[{flexDirection: 'row'}, s.mb5]}>
|
|
||||||
<Button type="primary" label="Primary solid" style={buttonStyles} />
|
|
||||||
<Button type="secondary" label="Secondary solid" style={buttonStyles} />
|
|
||||||
</View>
|
|
||||||
<View style={[{flexDirection: 'row'}, s.mb5]}>
|
|
||||||
<Button type="default" label="Default solid" style={buttonStyles} />
|
|
||||||
<Button type="inverted" label="Inverted solid" style={buttonStyles} />
|
|
||||||
</View>
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button
|
|
||||||
type="primary-outline"
|
|
||||||
label="Primary outline"
|
|
||||||
style={buttonStyles}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="secondary-outline"
|
|
||||||
label="Secondary outline"
|
|
||||||
style={buttonStyles}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button
|
|
||||||
type="primary-light"
|
|
||||||
label="Primary light"
|
|
||||||
style={buttonStyles}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="secondary-light"
|
|
||||||
label="Secondary light"
|
|
||||||
style={buttonStyles}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button
|
|
||||||
type="default-light"
|
|
||||||
label="Default light"
|
|
||||||
style={buttonStyles}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user