Couple of starter packs tweaks (#4604)

zio/stable
Hailey 2024-06-24 10:24:39 -07:00 committed by GitHub
parent f769564edf
commit 77a512ae32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 57 additions and 63 deletions

View File

@ -45,9 +45,7 @@ module.exports = function (config) {
'appclips:bsky.app', 'appclips:bsky.app',
'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes 'appclips:go.bsky.app', // Allows App Clip to work when scanning QR codes
// When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port. // When testing local services, enter an ngrok (et al) domain here. It must use a standard HTTP/HTTPS port.
...(IS_DEV || IS_TESTFLIGHT ...(IS_DEV || IS_TESTFLIGHT ? [] : []),
? ['appclips:sptesting.haileyok.com', 'applinks:sptesting.haileyok.com']
: []),
] ]
const UPDATES_CHANNEL = IS_TESTFLIGHT const UPDATES_CHANNEL = IS_TESTFLIGHT

View File

@ -1,16 +1,14 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import ViewShot from 'react-native-view-shot' import ViewShot from 'react-native-view-shot'
import * as FS from 'expo-file-system'
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
import {createAssetAsync} from 'expo-media-library'
import * as Sharing from 'expo-sharing' import * as Sharing from 'expo-sharing'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {nanoid} from 'nanoid/non-secure'
import {logger} from '#/logger' import {logger} from '#/logger'
import {saveImageToMediaLibrary} from 'lib/media/manip'
import {logEvent} from 'lib/statsig/statsig' import {logEvent} from 'lib/statsig/statsig'
import {isNative, isWeb} from 'platform/detection' import {isNative, isWeb} from 'platform/detection'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
@ -65,13 +63,9 @@ export function QrCodeDialog({
return return
} }
const filename = `${FS.documentDirectory}/${nanoid(12)}.png`
// Incase of a FS failure, don't crash the app // Incase of a FS failure, don't crash the app
try { try {
await FS.copyAsync({from: uri, to: filename}) await createAssetAsync(`file://${uri}`)
await saveImageToMediaLibrary({uri: filename})
await FS.deleteAsync(filename)
} catch (e: unknown) { } catch (e: unknown) {
Toast.show(_(msg`An error occurred while saving the QR code!`)) Toast.show(_(msg`An error occurred while saving the QR code!`))
logger.error('Failed to save QR code', {error: e}) logger.error('Failed to save QR code', {error: e})

View File

@ -1,12 +1,10 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import * as FS from 'expo-file-system'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
import {AppBskyGraphDefs} from '@atproto/api' import {AppBskyGraphDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {nanoid} from 'nanoid/non-secure'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -72,19 +70,8 @@ function ShareDialogInner({
return return
} }
const cachePath = await Image.getCachePathAsync(imageUrl)
const filename = `${FS.documentDirectory}/${nanoid(12)}.png`
if (!cachePath) {
Toast.show(_(msg`An error occurred while saving the image.`))
return
}
try { try {
await FS.copyAsync({from: cachePath, to: filename}) await saveImageToMediaLibrary({uri: imageUrl})
await saveImageToMediaLibrary({uri: filename})
await FS.deleteAsync(filename)
Toast.show(_(msg`Image saved to your camera roll!`)) Toast.show(_(msg`Image saved to your camera roll!`))
control.close() control.close()
} catch (e: unknown) { } catch (e: unknown) {
@ -133,18 +120,18 @@ function ShareDialogInner({
isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}], isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}],
]}> ]}>
<Button <Button
label="Share link" label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
style={[isWeb && a.self_center]} style={[isWeb && a.self_center]}
onPress={onShareLink}> onPress={onShareLink}>
<ButtonText> <ButtonText>
{isWeb ? <Trans>Copy Link</Trans> : <Trans>Share Link</Trans>} {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>}
</ButtonText> </ButtonText>
</Button> </Button>
<Button <Button
label="Create QR code" label={_(msg`Share QR code`)}
variant="solid" variant="solid"
color="secondary" color="secondary"
size="small" size="small"
@ -155,7 +142,7 @@ function ShareDialogInner({
}) })
}}> }}>
<ButtonText> <ButtonText>
<Trans>Create QR code</Trans> <Trans>Share QR code</Trans>
</ButtonText> </ButtonText>
</Button> </Button>
{isNative && ( {isNative && (

View File

@ -58,6 +58,7 @@ export function WizardEditListDialog({
state.currentStep === 'Profiles' ? ( state.currentStep === 'Profiles' ? (
<WizardProfileCard <WizardProfileCard
profile={item} profile={item}
btnType="remove"
state={state} state={state}
dispatch={dispatch} dispatch={dispatch}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}
@ -65,6 +66,7 @@ export function WizardEditListDialog({
) : ( ) : (
<WizardFeedCard <WizardFeedCard
generator={item} generator={item}
btnType="remove"
state={state} state={state}
dispatch={dispatch} dispatch={dispatch}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}

View File

@ -9,7 +9,7 @@ import {
ModerationUI, ModerationUI,
} from '@atproto/api' } from '@atproto/api'
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
import {msg} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {DISCOVER_FEED_URI} from 'lib/constants' import {DISCOVER_FEED_URI} from 'lib/constants'
@ -19,12 +19,14 @@ import {useSession} from 'state/session'
import {UserAvatar} from 'view/com/util/UserAvatar' import {UserAvatar} from 'view/com/util/UserAvatar'
import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Toggle from '#/components/forms/Toggle' import * as Toggle from '#/components/forms/Toggle'
import {Checkbox} from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
function WizardListCard({ function WizardListCard({
type, type,
btnType,
displayName, displayName,
subtitle, subtitle,
onPress, onPress,
@ -34,6 +36,7 @@ function WizardListCard({
moderationUi, moderationUi,
}: { }: {
type: 'user' | 'algo' type: 'user' | 'algo'
btnType: 'checkbox' | 'remove'
profile?: AppBskyActorDefs.ProfileViewBasic profile?: AppBskyActorDefs.ProfileViewBasic
feed?: AppBskyFeedDefs.GeneratorView feed?: AppBskyFeedDefs.GeneratorView
displayName: string displayName: string
@ -56,7 +59,7 @@ function WizardListCard({
: _(msg`Add ${displayName} to starter pack`) : _(msg`Add ${displayName} to starter pack`)
} }
value={included} value={included}
disabled={disabled} disabled={btnType === 'remove' || disabled}
onChange={onPress} onChange={onPress}
style={[ style={[
a.flex_row, a.flex_row,
@ -85,17 +88,33 @@ function WizardListCard({
{subtitle} {subtitle}
</Text> </Text>
</View> </View>
<Checkbox /> {btnType === 'checkbox' ? (
<Checkbox />
) : !disabled ? (
<Button
label={_(msg`Remove`)}
variant="solid"
color="secondary"
size="xsmall"
style={[a.self_center, {marginLeft: 'auto'}]}
onPress={onPress}>
<ButtonText>
<Trans>Remove</Trans>
</ButtonText>
</Button>
) : null}
</Toggle.Item> </Toggle.Item>
) )
} }
export function WizardProfileCard({ export function WizardProfileCard({
btnType,
state, state,
dispatch, dispatch,
profile, profile,
moderationOpts, moderationOpts,
}: { }: {
btnType: 'checkbox' | 'remove'
state: WizardState state: WizardState
dispatch: (action: WizardAction) => void dispatch: (action: WizardAction) => void
profile: AppBskyActorDefs.ProfileViewBasic profile: AppBskyActorDefs.ProfileViewBasic
@ -127,6 +146,7 @@ export function WizardProfileCard({
return ( return (
<WizardListCard <WizardListCard
type="user" type="user"
btnType={btnType}
displayName={displayName} displayName={displayName}
subtitle={`@${sanitizeHandle(profile.handle)}`} subtitle={`@${sanitizeHandle(profile.handle)}`}
onPress={onPress} onPress={onPress}
@ -139,11 +159,13 @@ export function WizardProfileCard({
} }
export function WizardFeedCard({ export function WizardFeedCard({
btnType,
generator, generator,
state, state,
dispatch, dispatch,
moderationOpts, moderationOpts,
}: { }: {
btnType: 'checkbox' | 'remove'
generator: GeneratorView generator: GeneratorView
state: WizardState state: WizardState
dispatch: (action: WizardAction) => void dispatch: (action: WizardAction) => void
@ -170,6 +192,7 @@ export function WizardFeedCard({
return ( return (
<WizardListCard <WizardListCard
type="algo" type="algo"
btnType={btnType}
displayName={sanitizeDisplayName(generator.displayName)} displayName={sanitizeDisplayName(generator.displayName)}
subtitle={`Feed by @${sanitizeHandle(generator.creator.handle)}`} subtitle={`Feed by @${sanitizeHandle(generator.creator.handle)}`}
onPress={onPress} onPress={onPress}

View File

@ -40,12 +40,14 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
const {data: popularFeedsPages, fetchNextPage} = useGetPopularFeedsQuery({ const {data: popularFeedsPages, fetchNextPage} = useGetPopularFeedsQuery({
limit: 30, limit: 30,
}) })
const popularFeeds = const popularFeeds = popularFeedsPages?.pages.flatMap(p => p.feeds) ?? []
popularFeedsPages?.pages
.flatMap(page => page.feeds)
.filter(f => !savedFeeds?.some(sf => sf?.uri === f.uri)) ?? []
const suggestedFeeds = savedFeeds?.concat(popularFeeds) const suggestedFeeds =
savedFeeds.length === 0
? popularFeeds
: savedFeeds.concat(
popularFeeds.filter(f => !savedFeeds.some(sf => sf.uri === f.uri)),
)
const {data: searchedFeeds, isLoading: isLoadingSearch} = const {data: searchedFeeds, isLoading: isLoadingSearch} =
useSearchPopularFeedsQuery({q: throttledQuery}) useSearchPopularFeedsQuery({q: throttledQuery})
@ -56,6 +58,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) {
return ( return (
<WizardFeedCard <WizardFeedCard
generator={item} generator={item}
btnType="checkbox"
state={state} state={state}
dispatch={dispatch} dispatch={dispatch}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}

View File

@ -45,6 +45,7 @@ export function StepProfiles({
return ( return (
<WizardProfileCard <WizardProfileCard
profile={item} profile={item}
btnType="checkbox"
state={state} state={state}
dispatch={dispatch} dispatch={dispatch}
moderationOpts={moderationOpts} moderationOpts={moderationOpts}

View File

@ -21,6 +21,7 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {logger} from '#/logger' import {logger} from '#/logger'
import {HITSLOP_10} from 'lib/constants' import {HITSLOP_10} from 'lib/constants'
import {createSanitizedDisplayName} from 'lib/moderation/create-sanitized-display-name'
import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
import {logEvent} from 'lib/statsig/statsig' import {logEvent} from 'lib/statsig/statsig'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
@ -170,15 +171,7 @@ function WizardInner({
) )
const getDefaultName = () => { const getDefaultName = () => {
let displayName const displayName = createSanitizedDisplayName(currentProfile!, true)
if (
currentProfile?.displayName != null &&
currentProfile?.displayName !== ''
) {
displayName = sanitizeDisplayName(currentProfile.displayName)
} else {
displayName = sanitizeHandle(currentProfile!.handle)
}
return _(msg`${displayName}'s Starter Pack`).slice(0, 50) return _(msg`${displayName}'s Starter Pack`).slice(0, 50)
} }
@ -191,16 +184,12 @@ function WizardInner({
nextBtn: _(msg`Next`), nextBtn: _(msg`Next`),
}, },
Profiles: { Profiles: {
header: _(msg`People`), header: _(msg`Choose People`),
nextBtn: _(msg`Next`), nextBtn: _(msg`Next`),
subtitle: _(
msg`Add people to your starter pack that you think others will enjoy following`,
),
}, },
Feeds: { Feeds: {
header: _(msg`Feeds`), header: _(msg`Choose Feeds`),
nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`), nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`),
subtitle: _(msg`Some subtitle`),
}, },
} }
const currUiStrings = wizardUiStrings[state.currentStep] const currUiStrings = wizardUiStrings[state.currentStep]
@ -254,8 +243,8 @@ function WizardInner({
dispatch({type: 'SetProcessing', processing: true}) dispatch({type: 'SetProcessing', processing: true})
if (currentStarterPack && currentListItems) { if (currentStarterPack && currentListItems) {
editStarterPack({ editStarterPack({
name: state.name ?? getDefaultName(), name: state.name?.trim() || getDefaultName(),
description: state.description, description: state.description?.trim(),
descriptionFacets: [], descriptionFacets: [],
profiles: state.profiles, profiles: state.profiles,
feeds: state.feeds, feeds: state.feeds,
@ -264,8 +253,8 @@ function WizardInner({
}) })
} else { } else {
createStarterPack({ createStarterPack({
name: state.name ?? getDefaultName(), name: state.name?.trim() || getDefaultName(),
description: state.description, description: state.description?.trim(),
descriptionFacets: [], descriptionFacets: [],
profiles: state.profiles, profiles: state.profiles,
feeds: state.feeds, feeds: state.feeds,
@ -483,13 +472,10 @@ function Footer({
</Trans> </Trans>
) : items.length === 2 ? ( ) : items.length === 2 ? (
<Trans> <Trans>
<Text style={[a.font_bold, textStyles]}> <Text style={[a.font_bold, textStyles]}>You</Text> and
{getName(items[initialNamesIndex])}{' '}
</Text>
and
<Text> </Text> <Text> </Text>
<Text style={[a.font_bold, textStyles]}> <Text style={[a.font_bold, textStyles]}>
{getName(items[state.currentStep === 'Profiles' ? 0 : 1])}{' '} {getName(items[initialNamesIndex])}{' '}
</Text> </Text>
are included in your starter pack are included in your starter pack
</Trans> </Trans>
@ -579,9 +565,9 @@ function Footer({
function getName(item: AppBskyActorDefs.ProfileViewBasic | GeneratorView) { function getName(item: AppBskyActorDefs.ProfileViewBasic | GeneratorView) {
if (typeof item.displayName === 'string') { if (typeof item.displayName === 'string') {
return enforceLen(sanitizeDisplayName(item.displayName), 16, true) return enforceLen(sanitizeDisplayName(item.displayName), 28, true)
} else if (typeof item.handle === 'string') { } else if (typeof item.handle === 'string') {
return enforceLen(sanitizeHandle(item.handle), 16, true) return enforceLen(sanitizeHandle(item.handle), 28, true)
} }
return '' return ''
} }