Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (33 commits)
  Improved server selector during account creation and signin (#2840)
  remove line height from text input for composer on ios (#2844)
  Add follow button to highlighted post (#2828)
  Cleaning up old codes pt_BR (#2809)
  Fix wrong translations in zh-CN localization  (#2812)
  Adjust Japanese translation (#2814)
  Update ja messages.po (#2821)
  change follow to follow all when !== 20 (#2831)
  ios adult content link fix 🤦 (#2845)
  improves build.md completeness (#2835)
  increase stroke width for active hashtag icon (#2829)
  Update `blueskyweb.xyz` links to `bsky.social` (#2830)
  eas nightlies (#2826)
  Always show post dropdown button at the bottom of the post, add share button to highlighted post (#2646)
  Measure header layout reliably (#2817)
  Round line height (#2824)
  Design system tweaks (#2822)
  Fix layout calculations (#2816)
  Fix flashes and jumps when opening profile (#2815)
  Show text cursor on web bio (#2813)
  ...
This commit is contained in:
Eric Bailey
2024-02-12 15:43:33 -06:00
90 changed files with 1781 additions and 1707 deletions
@@ -1,6 +1,6 @@
import React from 'react'
import {LabelPreference} from '@atproto/api'
import {StyleSheet, Pressable, View} from 'react-native'
import {StyleSheet, Pressable, View, Linking} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {ScrollView} from './util'
import {s, colors, gradients} from 'lib/styles'
@@ -129,6 +129,10 @@ function AdultContentEnabledPref() {
}
}, [variables, preferences, mutate, _])
const onAdultContentLinkPress = React.useCallback(() => {
Linking.openURL('https://bsky.app/')
}, [])
return (
<View style={s.mb10}>
{isIOS ? (
@@ -138,8 +142,9 @@ function AdultContentEnabledPref() {
Adult content can only be enabled via the Web at{' '}
<TextLink
style={pal.link}
href="https://bsky.app"
href=""
text="bsky.app"
onPress={onAdultContentLinkPress}
/>
.
</Trans>
+2 -39
View File
@@ -1,19 +1,13 @@
import React, {useRef, useEffect} from 'react'
import {StyleSheet} from 'react-native'
import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
import {SafeAreaView} from 'react-native-safe-area-context'
import BottomSheet from '@gorhom/bottom-sheet'
import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
import {usePalette} from 'lib/hooks/usePalette'
import {timeout} from 'lib/async/timeout'
import {navigate} from '../../../Navigation'
import once from 'lodash.once'
import {useModals, useModalControls} from '#/state/modals'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview'
import * as ServerInputModal from './ServerInput'
import * as RepostModal from './Repost'
import * as SelfLabelModal from './SelfLabel'
import * as ThreadgateModal from './Threadgate'
@@ -50,34 +44,14 @@ export function ModalsContainer() {
const {closeModal} = useModalControls()
const bottomSheetRef = useRef<BottomSheet>(null)
const pal = usePalette('default')
const safeAreaInsets = useSafeAreaInsets()
const activeModal = activeModals[activeModals.length - 1]
const navigateOnce = once(navigate)
// It seems like the bottom sheet bugs out when this callback changes.
const onBottomSheetAnimate = useNonReactiveCallback(
(_fromIndex: number, toIndex: number) => {
if (activeModal?.name === 'profile-preview' && toIndex === 1) {
// begin loading the profile screen behind the scenes
navigateOnce('Profile', {name: activeModal.did})
}
},
)
const onBottomSheetChange = async (snapPoint: number) => {
if (snapPoint === -1) {
closeModal()
} else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
await navigateOnce('Profile', {name: activeModal.did})
// There is no particular callback for when the view has actually been presented.
// This delay gives us a decent chance the navigation has flushed *and* images have loaded.
// It's acceptable because the data is already being fetched + it usually takes longer anyway.
// TODO: Figure out why avatar/cover don't always show instantly from cache.
await timeout(200)
closeModal()
}
}
const onClose = () => {
bottomSheetRef.current?.close()
closeModal()
@@ -91,7 +65,6 @@ export function ModalsContainer() {
}
}, [isModalActive, bottomSheetRef, activeModal?.name])
let needsSafeTopInset = false
let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
let element
if (activeModal?.name === 'confirm') {
@@ -100,13 +73,6 @@ export function ModalsContainer() {
} else if (activeModal?.name === 'edit-profile') {
snapPoints = EditProfileModal.snapPoints
element = <EditProfileModal.Component {...activeModal} />
} else if (activeModal?.name === 'profile-preview') {
snapPoints = ProfilePreviewModal.snapPoints
element = <ProfilePreviewModal.Component {...activeModal} />
needsSafeTopInset = true // Need to align with the target profile screen.
} else if (activeModal?.name === 'server-input') {
snapPoints = ServerInputModal.snapPoints
element = <ServerInputModal.Component {...activeModal} />
} else if (activeModal?.name === 'report') {
snapPoints = ReportModal.snapPoints
element = <ReportModal.Component {...activeModal} />
@@ -200,12 +166,10 @@ export function ModalsContainer() {
)
}
const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0
return (
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
topInset={topInset}
handleHeight={HANDLE_HEIGHT}
index={isModalActive ? 0 : -1}
enablePanDownToClose
@@ -216,7 +180,6 @@ export function ModalsContainer() {
}
handleIndicatorStyle={{backgroundColor: pal.text.color}}
handleStyle={[styles.handle, pal.view]}
onAnimate={onBottomSheetAnimate}
onChange={onBottomSheetChange}>
{element}
</BottomSheet>
-6
View File
@@ -9,8 +9,6 @@ import {useModals, useModalControls} from '#/state/modals'
import type {Modal as ModalIface} from '#/state/modals'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
import * as ProfilePreviewModal from './ProfilePreview'
import * as ServerInputModal from './ServerInput'
import * as ReportModal from './report/Modal'
import * as AppealLabelModal from './AppealLabel'
import * as CreateOrEditListModal from './CreateOrEditList'
@@ -85,10 +83,6 @@ function Modal({modal}: {modal: ModalIface}) {
element = <ConfirmModal.Component {...modal} />
} else if (modal.name === 'edit-profile') {
element = <EditProfileModal.Component {...modal} />
} else if (modal.name === 'profile-preview') {
element = <ProfilePreviewModal.Component {...modal} />
} else if (modal.name === 'server-input') {
element = <ServerInputModal.Component {...modal} />
} else if (modal.name === 'report') {
element = <ReportModal.Component {...modal} />
} else if (modal.name === 'appeal-label') {
-134
View File
@@ -1,134 +0,0 @@
import React, {useState, useEffect} from 'react'
import {ActivityIndicator, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs, ModerationOpts, moderateProfile} from '@atproto/api'
import {ThemedText} from '../util/text/ThemedText'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {ProfileHeader} from '../profile/ProfileHeader'
import {InfoCircleIcon} from 'lib/icons'
import {useNavigationState} from '@react-navigation/native'
import {s} from 'lib/styles'
import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileQuery} from '#/state/queries/profile'
import {ErrorScreen} from '../util/error/ErrorScreen'
import {CenteredView} from '../util/Views'
import {cleanError} from '#/lib/strings/errors'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const snapPoints = [520, '100%']
export function Component({did}: {did: string}) {
const pal = usePalette('default')
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const {
data: profile,
error: profileError,
refetch: refetchProfile,
isLoading: isLoadingProfile,
} = useProfileQuery({
did: did,
})
if (isLoadingProfile || !moderationOpts) {
return (
<CenteredView style={[pal.view, s.flex1]}>
<ProfileHeader
profile={null}
moderation={null}
isProfilePreview={true}
/>
</CenteredView>
)
}
if (profileError) {
return (
<ErrorScreen
title={_(msg`Oops!`)}
message={cleanError(profileError)}
onPressTryAgain={refetchProfile}
/>
)
}
if (profile && moderationOpts) {
return <ComponentLoaded profile={profile} moderationOpts={moderationOpts} />
}
// should never happen
return (
<ErrorScreen
title={_(msg`Oops!`)}
message={_(msg`Something went wrong and we're not sure what.`)}
onPressTryAgain={refetchProfile}
/>
)
}
function ComponentLoaded({
profile: profileUnshadowed,
moderationOpts,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
}) {
const pal = usePalette('default')
const profile = useProfileShadow(profileUnshadowed)
const {screen} = useAnalytics()
const moderation = React.useMemo(
() => moderateProfile(profile, moderationOpts),
[profile, moderationOpts],
)
// track the navigator state to detect if a page-load occurred
const navState = useNavigationState(state => state)
const [initNavState] = useState(navState)
const isLoading = initNavState !== navState
useEffect(() => {
screen('Profile:Preview')
}, [screen])
return (
<View testID="profilePreview" style={[pal.view, s.flex1]}>
<View style={[styles.headerWrapper]}>
<ProfileHeader
profile={profile}
moderation={moderation}
hideBackButton
isProfilePreview
/>
</View>
<View style={[styles.hintWrapper, pal.view]}>
<View style={styles.hint}>
{isLoading ? (
<ActivityIndicator />
) : (
<>
<InfoCircleIcon size={21} style={pal.textLight} />
<ThemedText type="xl" fg="light">
<Trans>Swipe up to see more</Trans>
</ThemedText>
</>
)}
</View>
</View>
</View>
)
}
const styles = StyleSheet.create({
headerWrapper: {
height: 440,
},
hintWrapper: {
height: 80,
},
hint: {
flexDirection: 'row',
justifyContent: 'center',
gap: 8,
paddingHorizontal: 14,
borderRadius: 6,
},
})
-189
View File
@@ -1,189 +0,0 @@
import React, {useState} from 'react'
import {Platform, StyleSheet, TouchableOpacity, View} from 'react-native'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
import {ScrollView, TextInput} from './util'
import {Text} from '../util/text/Text'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'lib/constants'
import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%']
export function Component({onSelect}: {onSelect: (url: string) => void}) {
const theme = useTheme()
const pal = usePalette('default')
const [customUrl, setCustomUrl] = useState<string>('')
const {_} = useLingui()
const {closeModal} = useModalControls()
const doSelect = (url: string) => {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `https://${url}`
}
closeModal()
onSelect(url)
}
return (
<View style={[pal.view, s.flex1]} testID="serverInputModal">
<Text type="2xl-bold" style={[pal.text, s.textCenter]}>
<Trans>Choose Service</Trans>
</Text>
<ScrollView style={styles.inner}>
<View style={styles.group}>
{LOGIN_INCLUDE_DEV_SERVERS ? (
<>
<TouchableOpacity
testID="localDevServerButton"
style={styles.btn}
onPress={() => doSelect(LOCAL_DEV_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>
<Trans>Local dev server</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
<TouchableOpacity
style={styles.btn}
onPress={() => doSelect(STAGING_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>
<Trans>Staging</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
</>
) : undefined}
<TouchableOpacity
style={styles.btn}
onPress={() => doSelect(PROD_SERVICE)}
accessibilityRole="button"
accessibilityLabel={_(msg`Select Bluesky Social`)}
accessibilityHint="Sets Bluesky Social as your service provider">
<Text style={styles.btnText}>
<Trans>Bluesky.Social</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
/>
</TouchableOpacity>
</View>
<View style={styles.group}>
<Text style={[pal.text, styles.label]}>
<Trans>Other service</Trans>
</Text>
<View style={s.flexRow}>
<TextInput
testID="customServerTextInput"
style={[pal.borderDark, pal.text, styles.textInput]}
placeholder="e.g. https://bsky.app"
placeholderTextColor={colors.gray4}
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
keyboardAppearance={theme.colorScheme}
value={customUrl}
onChangeText={setCustomUrl}
accessibilityLabel={_(msg`Custom domain`)}
// TODO: Simplify this wording further to be understandable by everyone
accessibilityHint={_(
msg`Use your domain as your Bluesky client service provider`,
)}
/>
<TouchableOpacity
testID="customServerSelectBtn"
style={[pal.borderDark, pal.text, styles.textInputBtn]}
onPress={() => doSelect(customUrl)}
accessibilityRole="button"
accessibilityLabel={`Confirm service. ${
customUrl === ''
? _(msg`Button disabled. Input custom domain to proceed.`)
: ''
}`}
accessibilityHint=""
// TODO - accessibility: Need to inform state change on failure
disabled={customUrl === ''}>
<FontAwesomeIcon
icon="check"
style={[pal.text as FontAwesomeIconStyle, styles.checkIcon]}
size={18}
/>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
inner: {
padding: 14,
},
group: {
marginBottom: 20,
},
label: {
fontWeight: 'bold',
paddingHorizontal: 4,
paddingBottom: 4,
},
textInput: {
flex: 1,
borderWidth: 1,
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
paddingHorizontal: 14,
paddingVertical: 12,
fontSize: 16,
},
textInputBtn: {
borderWidth: 1,
borderLeftWidth: 0,
borderTopRightRadius: 6,
borderBottomRightRadius: 6,
paddingHorizontal: 14,
paddingVertical: 10,
},
btn: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.blue3,
borderRadius: 6,
paddingHorizontal: 14,
paddingVertical: 10,
marginBottom: 6,
},
btnText: {
flex: 1,
fontSize: 18,
fontWeight: '500',
color: colors.white,
},
checkIcon: {
position: 'relative',
...Platform.select({
android: {
top: 8,
},
ios: {
top: 2,
},
}),
},
})
+1 -1
View File
@@ -18,7 +18,7 @@ import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
import {getAgent} from '#/state/session'
const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright'
const DMCA_LINK = 'https://bsky.social/about/support/copyright'
export const snapPoints = [575]