Merge branch 'main' into hailey/79fab
This commit is contained in:
@@ -5,12 +5,11 @@ import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {urls} from '#/lib/constants'
|
||||
import {compressIfNeeded} from '#/lib/media/manip'
|
||||
import {type PickerImage} from '#/lib/media/picker.shared'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {useWarnMaxGraphemeCount} from '#/lib/strings/helpers'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {type ImageMeta} from '#/state/gallery'
|
||||
import {useProfileUpdateMutation} from '#/state/queries/profile'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
@@ -18,10 +17,11 @@ import {EditableUserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {UserBanner} from '#/view/com/util/UserBanner'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {useSimpleVerificationState} from '#/components/verification'
|
||||
|
||||
@@ -127,10 +127,10 @@ function DialogInner({
|
||||
profile.avatar,
|
||||
)
|
||||
const [newUserBanner, setNewUserBanner] = useState<
|
||||
PickerImage | undefined | null
|
||||
ImageMeta | undefined | null
|
||||
>()
|
||||
const [newUserAvatar, setNewUserAvatar] = useState<
|
||||
PickerImage | undefined | null
|
||||
ImageMeta | undefined | null
|
||||
>()
|
||||
|
||||
const dirty =
|
||||
@@ -144,7 +144,7 @@ function DialogInner({
|
||||
}, [dirty, setDirty])
|
||||
|
||||
const onSelectNewAvatar = useCallback(
|
||||
async (img: PickerImage | null) => {
|
||||
(img: ImageMeta | null) => {
|
||||
setImageError('')
|
||||
if (img === null) {
|
||||
setNewUserAvatar(null)
|
||||
@@ -152,9 +152,8 @@ function DialogInner({
|
||||
return
|
||||
}
|
||||
try {
|
||||
const finalImg = await compressIfNeeded(img, 1000000)
|
||||
setNewUserAvatar(finalImg)
|
||||
setUserAvatar(finalImg.path)
|
||||
setNewUserAvatar(img)
|
||||
setUserAvatar(img.path)
|
||||
} catch (e: any) {
|
||||
setImageError(cleanError(e))
|
||||
}
|
||||
@@ -163,7 +162,7 @@ function DialogInner({
|
||||
)
|
||||
|
||||
const onSelectNewBanner = useCallback(
|
||||
async (img: PickerImage | null) => {
|
||||
(img: ImageMeta | null) => {
|
||||
setImageError('')
|
||||
if (!img) {
|
||||
setNewUserBanner(null)
|
||||
@@ -171,9 +170,8 @@ function DialogInner({
|
||||
return
|
||||
}
|
||||
try {
|
||||
const finalImg = await compressIfNeeded(img, 1000000)
|
||||
setNewUserBanner(finalImg)
|
||||
setUserBanner(finalImg.path)
|
||||
setNewUserBanner(img)
|
||||
setUserBanner(img.path)
|
||||
} catch (e: any) {
|
||||
setImageError(cleanError(e))
|
||||
}
|
||||
@@ -258,6 +256,7 @@ function DialogInner({
|
||||
<ButtonText style={[a.text_md, !dirty && t.atoms.text_contrast_low]}>
|
||||
<Trans>Save</Trans>
|
||||
</ButtonText>
|
||||
{isUpdatingProfile && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
),
|
||||
[
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, {memo, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
AppBskyLabelerDefs,
|
||||
type AppBskyActorDefs,
|
||||
type AppBskyLabelerDefs,
|
||||
moderateProfile,
|
||||
ModerationOpts,
|
||||
RichText as RichTextAPI,
|
||||
type ModerationOpts,
|
||||
type RichText as RichTextAPI,
|
||||
} from '@atproto/api'
|
||||
import {msg, Plural, plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -15,10 +15,9 @@ import {MAX_LABELERS} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {isAppLabeler} from '#/lib/moderation'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS, isWeb} from '#/platform/detection'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useLabelerSubscriptionMutation} from '#/state/queries/labeler'
|
||||
import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
@@ -27,7 +26,7 @@ import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {atoms as a, tokens, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {DialogOuterProps, useDialogControl} from '#/components/Dialog'
|
||||
import {type DialogOuterProps, useDialogControl} from '#/components/Dialog'
|
||||
import {
|
||||
Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
|
||||
Heart2_Stroke2_Corner0_Rounded as Heart,
|
||||
@@ -117,19 +116,7 @@ let ProfileHeaderLabeler = ({
|
||||
}
|
||||
}, [labeler, playHaptic, likeUri, unlikeMod, likeMod, _])
|
||||
|
||||
const {openModal} = useModalControls()
|
||||
const editProfileControl = useDialogControl()
|
||||
const onPressEditProfile = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
// temp, while we figure out the nested dialog bug
|
||||
openModal({
|
||||
name: 'edit-profile',
|
||||
profile,
|
||||
})
|
||||
} else {
|
||||
editProfileControl.open()
|
||||
}
|
||||
}, [editProfileControl, openModal, profile])
|
||||
|
||||
const onPressSubscribe = React.useCallback(
|
||||
() =>
|
||||
@@ -192,7 +179,7 @@ let ProfileHeaderLabeler = ({
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
onPress={onPressEditProfile}
|
||||
onPress={editProfileControl.open}
|
||||
label={_(msg`Edit profile`)}
|
||||
style={a.rounded_full}>
|
||||
<ButtonText>
|
||||
|
||||
@@ -12,10 +12,9 @@ import {useLingui} from '@lingui/react'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS, isWeb} from '#/platform/detection'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {type Shadow} from '#/state/cache/types'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {
|
||||
useProfileBlockMutationQueue,
|
||||
useProfileFollowMutationQueue,
|
||||
@@ -78,19 +77,7 @@ let ProfileHeaderStandard = ({
|
||||
profile.viewer?.blockedBy ||
|
||||
profile.viewer?.blockingByList
|
||||
|
||||
const {openModal} = useModalControls()
|
||||
const editProfileControl = useDialogControl()
|
||||
const onPressEditProfile = React.useCallback(() => {
|
||||
if (isWeb) {
|
||||
// temp, while we figure out the nested dialog bug
|
||||
openModal({
|
||||
name: 'edit-profile',
|
||||
profile,
|
||||
})
|
||||
} else {
|
||||
editProfileControl.open()
|
||||
}
|
||||
}, [editProfileControl, openModal, profile])
|
||||
|
||||
const onPressFollow = () => {
|
||||
requireAuth(async () => {
|
||||
@@ -178,7 +165,7 @@ let ProfileHeaderStandard = ({
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
onPress={onPressEditProfile}
|
||||
onPress={editProfileControl.open}
|
||||
label={_(msg`Edit profile`)}
|
||||
style={[a.rounded_full]}>
|
||||
<ButtonText>
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {
|
||||
type CommonNavigatorParams,
|
||||
type NativeStackScreenProps,
|
||||
} from '#/lib/routes/types'
|
||||
import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as Select from '#/components/Select'
|
||||
import {Text} from '#/components/Typography'
|
||||
import * as SettingsList from './components/SettingsList'
|
||||
|
||||
const DEDUPED_LANGUAGES = LANGUAGES.filter(
|
||||
(lang, i, arr) =>
|
||||
lang.code2 && arr.findIndex(l => l.code2 === lang.code2) === i,
|
||||
)
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
|
||||
export function LanguageSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
@@ -32,7 +39,7 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
}, [openModal])
|
||||
|
||||
const onChangePrimaryLanguage = useCallback(
|
||||
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
|
||||
(value: string) => {
|
||||
if (!value) return
|
||||
if (langPrefs.primaryLanguage !== value) {
|
||||
setLangPrefs.setPrimaryLanguage(value)
|
||||
@@ -42,7 +49,7 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
)
|
||||
|
||||
const onChangeAppLanguage = useCallback(
|
||||
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
|
||||
(value: string) => {
|
||||
if (!value) return
|
||||
if (langPrefs.appLanguage !== value) {
|
||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||
@@ -85,79 +92,26 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
Select which language to use for the app's user interface.
|
||||
</Trans>
|
||||
</Text>
|
||||
<View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}>
|
||||
<RNPickerSelect
|
||||
darkTheme={t.scheme === 'dark'}
|
||||
placeholder={{}}
|
||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||
onValueChange={onChangeAppLanguage}
|
||||
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
<Select.Root
|
||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||
onValueChange={onChangeAppLanguage}>
|
||||
<Select.Trigger label={_(msg`Select app language`)}>
|
||||
<Select.ValueText />
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
<Select.ItemText>{label}</Select.ItemText>
|
||||
</Select.Item>
|
||||
)}
|
||||
items={APP_LANGUAGES.map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
inputWeb: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.rounded_xs,
|
||||
a.pointer_events_none,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
{
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
},
|
||||
]}>
|
||||
<ChevronDownIcon style={[t.atoms.text]} />
|
||||
</View>
|
||||
</View>
|
||||
</Select.Root>
|
||||
</View>
|
||||
</SettingsList.Group>
|
||||
<SettingsList.Divider />
|
||||
@@ -171,77 +125,26 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
Select your preferred language for translations in your feed.
|
||||
</Trans>
|
||||
</Text>
|
||||
<View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}>
|
||||
<RNPickerSelect
|
||||
darkTheme={t.scheme === 'dark'}
|
||||
placeholder={{}}
|
||||
value={langPrefs.primaryLanguage}
|
||||
onValueChange={onChangePrimaryLanguage}
|
||||
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
<Select.Root
|
||||
value={langPrefs.primaryLanguage}
|
||||
onValueChange={onChangePrimaryLanguage}>
|
||||
<Select.Trigger label={_(msg`Select primary language`)}>
|
||||
<Select.ValueText />
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
<Select.ItemText>{label}</Select.ItemText>
|
||||
</Select.Item>
|
||||
)}
|
||||
items={DEDUPED_LANGUAGES.map(l => ({
|
||||
label: languageName(l, langPrefs.appLanguage),
|
||||
value: l.code2,
|
||||
key: l.code2 + l.code3,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
inputWeb: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
color: t.atoms.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: a.font_bold.fontWeight,
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
|
||||
borderRadius: a.rounded_xs.borderRadius,
|
||||
pointerEvents: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<ChevronDownIcon style={t.atoms.text} />
|
||||
</View>
|
||||
</View>
|
||||
</Select.Root>
|
||||
</View>
|
||||
</SettingsList.Group>
|
||||
<SettingsList.Divider />
|
||||
|
||||
@@ -184,10 +184,14 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
|
||||
<Divider />
|
||||
|
||||
<View
|
||||
style={[a.w_full, a.py_lg, a.flex_row, a.gap_lg, a.align_center]}>
|
||||
style={[a.w_full, a.py_lg, a.flex_row, a.gap_md, a.align_center]}>
|
||||
<AppLanguageDropdown />
|
||||
<Text
|
||||
style={[t.atoms.text_contrast_medium, !gtMobile && a.text_md]}>
|
||||
style={[
|
||||
a.flex_1,
|
||||
t.atoms.text_contrast_medium,
|
||||
!gtMobile && a.text_md,
|
||||
]}>
|
||||
<Trans>Having trouble?</Trans>{' '}
|
||||
<InlineLinkText
|
||||
label={_(msg`Contact support`)}
|
||||
|
||||
Reference in New Issue
Block a user