Merge remote-tracking branch 'origin/main' into hailey/embed-pack

This commit is contained in:
Hailey
2024-06-26 17:21:03 -07:00
27 changed files with 6243 additions and 4450 deletions
@@ -0,0 +1,51 @@
import React, {useCallback} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {isNative} from 'platform/detection'
import {Feed} from 'view/com/posts/Feed'
import {EmptyState} from 'view/com/util/EmptyState'
import {ListRef} from 'view/com/util/List'
import {SectionRef} from '#/screens/Profile/Sections/types'
interface ProfilesListProps {
listUri: string
headerHeight: number
scrollElRef: ListRef
}
export const PostsList = React.forwardRef<SectionRef, ProfilesListProps>(
function PostsListImpl({listUri, headerHeight, scrollElRef}, ref) {
const feed: FeedDescriptor = `list|${listUri}|as_following`
const {_} = useLingui()
const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({
animated: isNative,
offset: -headerHeight,
})
}, [scrollElRef, headerHeight])
React.useImperativeHandle(ref, () => ({
scrollToTop: onScrollToTop,
}))
const renderPostsEmpty = useCallback(() => {
return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
}, [_])
return (
<View>
<Feed
feed={feed}
pollInterval={60e3}
scrollElRef={scrollElRef}
renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight}
/>
</View>
)
},
)
@@ -232,11 +232,13 @@ function Empty() {
t.atoms.text_contrast_medium,
{color: 'white'},
]}>
You haven't created a starter pack yet!
<Trans>You haven't created a starter pack yet!</Trans>
</Text>
<Text style={[a.text_md, {color: 'white'}]}>
Starter packs let you easily share your favorite feeds and people with
your friends.
<Trans>
Starter packs let you easily share your favorite feeds and people
with your friends.
</Trans>
</Text>
</View>
<View style={[a.flex_row, a.gap_md, {marginLeft: 'auto'}]}>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+25 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'
import {View} from 'react-native'
import {Linking, View} from 'react-native'
import {useSafeAreaFrame} from 'react-native-safe-area-context'
import {ComAtprotoLabelDefs} from '@atproto/api'
import {LABELS} from '@atproto/api'
@@ -10,6 +10,7 @@ import {useFocusEffect} from '@react-navigation/native'
import {getLabelingServiceTitle} from '#/lib/moderation'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {logger} from '#/logger'
import {isIOS} from '#/platform/detection'
import {
useMyLabelersQuery,
usePreferencesQuery,
@@ -202,6 +203,8 @@ export function ModerationScreenInner({
[setAdultContentPref],
)
const disabledOnIOS = isIOS && !adultContentEnabled
return (
<ScrollView
contentContainerStyle={[
@@ -324,12 +327,14 @@ export function ModerationScreenInner({
a.flex_row,
a.align_center,
a.justify_between,
disabledOnIOS && {opacity: 0.5},
]}>
<Text style={[a.font_semibold, t.atoms.text_contrast_high]}>
<Trans>Enable adult content</Trans>
</Text>
<Toggle.Item
label={_(msg`Toggle to enable or disable adult content`)}
disabled={disabledOnIOS}
name="adultContent"
value={adultContentEnabled}
onChange={onToggleAdultContentEnabled}>
@@ -345,6 +350,25 @@ export function ModerationScreenInner({
</View>
</Toggle.Item>
</View>
{disabledOnIOS && (
<View style={[a.pb_lg, a.px_lg]}>
<Text>
<Trans>
Adult content can only be enabled via the Web at{' '}
<InlineLinkText
to=""
onPress={evt => {
evt.preventDefault()
Linking.openURL('https://bsky.app/')
return false
}}>
bsky.app
</InlineLinkText>
.
</Trans>
</Text>
</View>
)}
<Divider />
</>
)}
@@ -1,16 +1,15 @@
import React from 'react'
import {View, ViewStyle, TextStyle} from 'react-native'
import {TextStyle, View, ViewStyle} from 'react-native'
import {useTheme, atoms as a, native} from '#/alf'
import {capitalize} from '#/lib/strings/capitalize'
import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
import {atoms as a, native, useTheme} from '#/alf'
import * as Toggle from '#/components/forms/Toggle'
import {Text} from '#/components/Typography'
import {capitalize} from '#/lib/strings/capitalize'
import {Context} from '#/screens/Onboarding/state'
export function InterestButton({interest}: {interest: string}) {
const t = useTheme()
const {interestsDisplayNames} = React.useContext(Context)
const interestsDisplayNames = useInterestsDisplayNames()
const ctx = Toggle.useItemContext()
const styles = React.useMemo(() => {
@@ -15,7 +15,11 @@ import {
OnboardingControls,
TitleText,
} from '#/screens/Onboarding/Layout'
import {ApiResponseMap, Context} from '#/screens/Onboarding/state'
import {
ApiResponseMap,
Context,
useInterestsDisplayNames,
} from '#/screens/Onboarding/state'
import {InterestButton} from '#/screens/Onboarding/StepInterests/InterestButton'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -33,8 +37,9 @@ export function StepInterests() {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {track} = useAnalytics()
const interestsDisplayNames = useInterestsDisplayNames()
const {state, dispatch, interestsDisplayNames} = React.useContext(Context)
const {state, dispatch} = React.useContext(Context)
const [saving, setSaving] = React.useState(false)
const [interests, setInterests] = React.useState<string[]>(
state.interestsStepResults.selectedInterests.map(i => i),
+32 -27
View File
@@ -1,4 +1,6 @@
import React from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {AvatarColor, Emoji} from '#/screens/Onboarding/StepProfile/types'
@@ -68,31 +70,36 @@ export type ApiResponseMap = {
}
}
export const INTEREST_TO_DISPLAY_NAME_DEFAULTS: {
[key: string]: string
} = {
news: 'News',
journalism: 'Journalism',
nature: 'Nature',
art: 'Art',
comics: 'Comics',
writers: 'Writers',
culture: 'Culture',
sports: 'Sports',
pets: 'Pets',
animals: 'Animals',
books: 'Books',
education: 'Education',
climate: 'Climate',
science: 'Science',
politics: 'Politics',
fitness: 'Fitness',
tech: 'Tech',
dev: 'Software Dev',
comedy: 'Comedy',
gaming: 'Video Games',
food: 'Food',
cooking: 'Cooking',
export function useInterestsDisplayNames() {
const {_} = useLingui()
return React.useMemo<Record<string, string>>(() => {
return {
// Keep this alphabetized
animals: _(msg`Animals`),
art: _(msg`Art`),
books: _(msg`Books`),
comedy: _(msg`Comedy`),
comics: _(msg`Comics`),
culture: _(msg`Culture`),
dev: _(msg`Software Dev`),
education: _(msg`Education`),
food: _(msg`Food`),
gaming: _(msg`Video Games`),
journalism: _(msg`Journalism`),
movies: _(msg`Movies`),
nature: _(msg`Nature`),
news: _(msg`News`),
pets: _(msg`Pets`),
photography: _(msg`Photography`),
politics: _(msg`Politics`),
science: _(msg`Science`),
sports: _(msg`Sports`),
tech: _(msg`Tech`),
tv: _(msg`TV`),
writers: _(msg`Writers`),
}
}, [_])
}
export const initialState: OnboardingState = {
@@ -120,11 +127,9 @@ export const initialState: OnboardingState = {
export const Context = React.createContext<{
state: OnboardingState
dispatch: React.Dispatch<OnboardingAction>
interestsDisplayNames: {[key: string]: string}
}>({
state: {...initialState},
dispatch: () => {},
interestsDisplayNames: INTEREST_TO_DISPLAY_NAME_DEFAULTS,
})
export function reducer(
+22 -6
View File
@@ -55,6 +55,7 @@ import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {RichText} from '#/components/RichText'
import {FeedsList} from '#/components/StarterPack/Main/FeedsList'
import {PostsList} from '#/components/StarterPack/Main/PostsList'
import {ProfilesList} from '#/components/StarterPack/Main/ProfilesList'
import {QrCodeDialog} from '#/components/StarterPack/QrCodeDialog'
import {ShareDialog} from '#/components/StarterPack/ShareDialog'
@@ -132,9 +133,14 @@ function StarterPackScreenInner({
>
moderationOpts: ModerationOpts
}) {
const showPeopleTab = Boolean(starterPack.list)
const showFeedsTab = Boolean(starterPack.feeds?.length)
const showPostsTab = Boolean(starterPack.list)
const tabs = [
...(starterPack.list ? ['People'] : []),
...(starterPack.feeds?.length ? ['Feeds'] : []),
...(showPeopleTab ? ['People'] : []),
...(showFeedsTab ? ['Feeds'] : []),
...(showPostsTab ? ['Posts'] : []),
]
const qrCodeDialogControl = useDialogControl()
@@ -180,10 +186,9 @@ function StarterPackScreenInner({
onOpenShareDialog={onOpenShareDialog}
/>
)}>
{starterPack.list != null
{showPeopleTab
? ({headerHeight, scrollElRef}) => (
<ProfilesList
key={0}
// Validated above
listUri={starterPack!.list!.uri}
headerHeight={headerHeight}
@@ -194,10 +199,9 @@ function StarterPackScreenInner({
/>
)
: null}
{starterPack.feeds != null
{showFeedsTab
? ({headerHeight, scrollElRef}) => (
<FeedsList
key={1}
// @ts-expect-error ?
feeds={starterPack?.feeds}
headerHeight={headerHeight}
@@ -206,6 +210,18 @@ function StarterPackScreenInner({
/>
)
: null}
{showPostsTab
? ({headerHeight, scrollElRef}) => (
<PostsList
// Validated above
listUri={starterPack!.list!.uri}
headerHeight={headerHeight}
// @ts-expect-error
scrollElRef={scrollElRef}
moderationOpts={moderationOpts}
/>
)
: null}
</PagerWithHeader>
</View>
+88 -55
View File
@@ -393,7 +393,6 @@ function Footer({
state.currentStep === 'Profiles'
? [profile, ...state.profiles]
: state.feeds
const initialNamesIndex = state.currentStep === 'Profiles' ? 1 : 0
const isEditEnabled =
(state.currentStep === 'Profiles' && items.length > 1) ||
@@ -445,65 +444,99 @@ function Footer({
))}
</View>
{items.length === 0 ? (
<View style={[a.gap_sm]}>
<Text style={[a.font_bold, a.text_center, textStyles]}>
<Trans>Add some feeds to your starter pack!</Trans>
</Text>
{
state.currentStep === 'Profiles' ? (
<Text style={[a.text_center, textStyles]}>
<Trans>Search for feeds that you want to suggest to others.</Trans>
{
items.length < 2 ? (
<Trans>
It's just you right now! Add more people to your starter pack
by searching above.
</Trans>
) : items.length === 2 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>You</Text> and
<Text> </Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[1] /* [0] is self, skip it */)}{' '}
</Text>
are included in your starter pack
</Trans>
) : items.length > 2 ? (
<Trans context="profiles">
<Text style={[a.font_bold, textStyles]}>
{getName(items[1] /* [0] is self, skip it */)},{' '}
</Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[2])},{' '}
</Text>
and{' '}
<Plural
value={items.length - 2}
one="# other"
other="# others"
/>{' '}
are included in your starter pack
</Trans>
) : null /* Should not happen. */
}
</Text>
</View>
) : (
<Text style={[a.text_center, textStyles]}>
{state.currentStep === 'Profiles' && items.length === 1 ? (
<Trans>
It's just you right now! Add more people to your starter pack by
searching above.
</Trans>
) : items.length === 1 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex])}
</Text>{' '}
is included in your starter pack
</Trans>
) : items.length === 2 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>You</Text> and
<Text> </Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex])}{' '}
) : state.currentStep === 'Feeds' ? (
items.length === 0 ? (
<View style={[a.gap_sm]}>
<Text style={[a.font_bold, a.text_center, textStyles]}>
<Trans>Add some feeds to your starter pack!</Trans>
</Text>
are included in your starter pack
</Trans>
) : state.currentStep === 'Profiles' ? (
<Trans context="profiles">
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex])},{' '}
<Text style={[a.text_center, textStyles]}>
<Trans>
Search for feeds that you want to suggest to others.
</Trans>
</Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex + 1])},{' '}
</Text>
and{' '}
<Plural value={items.length - 2} one="# other" other="# others" />{' '}
are included in your starter pack
</Trans>
</View>
) : (
<Trans context="feeds">
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex])},{' '}
</Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[initialNamesIndex + 1])},{' '}
</Text>
and{' '}
<Plural value={items.length - 2} one="# other" other="# others" />{' '}
are included in your starter pack
</Trans>
)}
</Text>
)}
<Text style={[a.text_center, textStyles]}>
{
items.length === 1 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>
{getName(items[0])}
</Text>{' '}
is included in your starter pack
</Trans>
) : items.length === 2 ? (
<Trans>
<Text style={[a.font_bold, textStyles]}>
{getName(items[0])}
</Text>{' '}
and
<Text> </Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[1])}{' '}
</Text>
are included in your starter pack
</Trans>
) : items.length > 2 ? (
<Trans context="feeds">
<Text style={[a.font_bold, textStyles]}>
{getName(items[0])},{' '}
</Text>
<Text style={[a.font_bold, textStyles]}>
{getName(items[1])},{' '}
</Text>
and{' '}
<Plural
value={items.length - 2}
one="# other"
other="# others"
/>{' '}
are included in your starter pack
</Trans>
) : null /* Should not happen. */
}
</Text>
)
) : null /* Should not happen. */
}
<View
style={[
+28 -3
View File
@@ -19,7 +19,34 @@ export function useFeedTuners(feedDesc: FeedDescriptor) {
]
}
if (feedDesc.startsWith('list')) {
return [FeedTuner.dedupReposts]
const feedTuners = []
if (feedDesc.endsWith('|as_following')) {
// Same as Following tuners below, copypaste for now.
if (preferences?.feedViewPrefs.hideReposts) {
feedTuners.push(FeedTuner.removeReposts)
} else {
feedTuners.push(FeedTuner.dedupReposts)
}
if (preferences?.feedViewPrefs.hideReplies) {
feedTuners.push(FeedTuner.removeReplies)
} else {
feedTuners.push(
FeedTuner.thresholdRepliesOnly({
userDid: currentAccount?.did || '',
minLikes: preferences?.feedViewPrefs.hideRepliesByLikeCount || 0,
followedOnly:
!!preferences?.feedViewPrefs.hideRepliesByUnfollowed,
}),
)
}
if (preferences?.feedViewPrefs.hideQuotePosts) {
feedTuners.push(FeedTuner.removeQuotePosts)
}
} else {
feedTuners.push(FeedTuner.dedupReposts)
}
return feedTuners
}
if (feedDesc === 'following') {
const feedTuners = []
@@ -29,7 +56,6 @@ export function useFeedTuners(feedDesc: FeedDescriptor) {
} else {
feedTuners.push(FeedTuner.dedupReposts)
}
if (preferences?.feedViewPrefs.hideReplies) {
feedTuners.push(FeedTuner.removeReplies)
} else {
@@ -41,7 +67,6 @@ export function useFeedTuners(feedDesc: FeedDescriptor) {
}),
)
}
if (preferences?.feedViewPrefs.hideQuotePosts) {
feedTuners.push(FeedTuner.removeQuotePosts)
}
+2
View File
@@ -49,6 +49,7 @@ type AuthorFilter =
| 'posts_with_media'
type FeedUri = string
type ListUri = string
type ListFilter = 'as_following' // Applies current Following settings. Currently client-side.
export type FeedDescriptor =
| 'following'
@@ -56,6 +57,7 @@ export type FeedDescriptor =
| `feedgen|${FeedUri}`
| `likes|${ActorDid}`
| `list|${ListUri}`
| `list|${ListUri}|${ListFilter}`
export interface FeedParams {
disableTuner?: boolean
mergeFeedEnabled?: boolean