Compare commits
19 Commits
app-1366
...
1.92.1-ota-6
| Author | SHA1 | Date | |
|---|---|---|---|
| dfbfcddef5 | |||
| 66b3f359f8 | |||
| 3be702cbf6 | |||
| fa55e37775 | |||
| 7dd0171afb | |||
| 89cff31b61 | |||
| 3fd8c06138 | |||
| 3229511849 | |||
| 754cb25137 | |||
| 1a992427e5 | |||
| c0456b0da3 | |||
| 1557a193d5 | |||
| ac4c68bb65 | |||
| c70ffc5737 | |||
| e98ba06162 | |||
| 9df712b5a7 | |||
| 31c8d7d8cb | |||
| 65500294c5 | |||
| 968eece57f |
@@ -23,7 +23,9 @@ func ExpandPostText(post *appbsky.FeedPost) string {
|
||||
}
|
||||
|
||||
// bail out if bounds checks fail
|
||||
if int(facet.Index.ByteStart)+charsAdded > len(postText) || int(facet.Index.ByteEnd)+charsAdded > len(postText) {
|
||||
if facet.Index.ByteStart > facet.Index.ByteEnd ||
|
||||
int(facet.Index.ByteStart)+charsAdded > len(postText) ||
|
||||
int(facet.Index.ByteEnd)+charsAdded > len(postText) {
|
||||
return false
|
||||
}
|
||||
linkText := postText[int(facet.Index.ByteStart)+charsAdded : int(facet.Index.ByteEnd)+charsAdded]
|
||||
|
||||
@@ -38,6 +38,8 @@ type Server struct {
|
||||
httpd *http.Server
|
||||
xrpcc *xrpc.Client
|
||||
cfg *Config
|
||||
|
||||
ipccClient http.Client
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -105,6 +107,13 @@ func serve(cctx *cli.Context) error {
|
||||
ipccHost: ipccHost,
|
||||
staticCDNHost: staticCDNHost,
|
||||
},
|
||||
ipccClient: http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create the HTTP server.
|
||||
@@ -584,15 +593,8 @@ func (srv *Server) WebIpCC(c echo.Context) error {
|
||||
}
|
||||
ipccUrlBuilder.Path = "ipccdata.IpCcService/Lookup"
|
||||
ipccUrl := ipccUrlBuilder.String()
|
||||
cl := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
postBodyReader := bytes.NewReader(request)
|
||||
response, err := cl.Post(ipccUrl, "application/json", postBodyReader)
|
||||
response, err := srv.ipccClient.Post(ipccUrl, "application/json", postBodyReader)
|
||||
if err != nil {
|
||||
log.Warnf("ipcc backend error %s", err)
|
||||
return c.JSON(500, IPCCResponse{})
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.93.0",
|
||||
"version": "1.92.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import {useFonts} from 'expo-font'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {Device, device} from '#/storage'
|
||||
|
||||
const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
|
||||
const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"`
|
||||
|
||||
const factor = 0.0625 // 1 - (15/16)
|
||||
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
|
||||
@@ -48,7 +48,7 @@ export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily += `, ${FAMILIES}`
|
||||
style.fontFamily += `, ${WEB_FONT_FAMILIES}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
} else {
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily = style.fontFamily || FAMILIES
|
||||
style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from 'react-native'
|
||||
import {
|
||||
KeyboardAwareScrollView,
|
||||
useKeyboardController,
|
||||
useKeyboardHandler,
|
||||
} from 'react-native-keyboard-controller'
|
||||
import {runOnJS} from 'react-native-reanimated'
|
||||
@@ -189,7 +190,21 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
function ScrollableInner({children, style, ...props}, ref) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||
const insets = useSafeAreaInsets()
|
||||
const {setEnabled} = useKeyboardController()
|
||||
|
||||
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isIOS) {
|
||||
return
|
||||
}
|
||||
|
||||
setEnabled(true)
|
||||
return () => {
|
||||
setEnabled(false)
|
||||
}
|
||||
})
|
||||
|
||||
useKeyboardHandler({
|
||||
onEnd: e => {
|
||||
'worklet'
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {atoms as a, useBreakpoints} from '#/alf'
|
||||
import {Button, 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 {Text} from '#/components/Typography'
|
||||
|
||||
export function VerifyEmailDialog({
|
||||
control,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
}) {
|
||||
const agent = useAgent()
|
||||
|
||||
const [didVerify, setDidVerify] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Dialog.Outer
|
||||
control={control}
|
||||
onClose={async () => {
|
||||
if (!didVerify) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await agent.resumeSession(agent.session!)
|
||||
} catch (e: unknown) {
|
||||
logger.error(String(e))
|
||||
return
|
||||
}
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<Inner control={control} setDidVerify={setDidVerify} />
|
||||
</Dialog.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
export function Inner({
|
||||
control,
|
||||
setDidVerify,
|
||||
}: {
|
||||
control: Dialog.DialogControlProps
|
||||
setDidVerify: (value: boolean) => void
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const agent = useAgent()
|
||||
const {openModal} = useModalControls()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
|
||||
const [currentStep, setCurrentStep] = React.useState<
|
||||
'StepOne' | 'StepTwo' | 'StepThree'
|
||||
>('StepOne')
|
||||
const [confirmationCode, setConfirmationCode] = React.useState('')
|
||||
const [isProcessing, setIsProcessing] = React.useState(false)
|
||||
const [error, setError] = React.useState('')
|
||||
|
||||
const uiStrings = {
|
||||
StepOne: {
|
||||
title: _(msg`Verify Your Email`),
|
||||
message: '',
|
||||
},
|
||||
StepTwo: {
|
||||
title: _(msg`Enter Code`),
|
||||
message: _(
|
||||
msg`An email has been sent! Please enter the confirmation code included in the email below.`,
|
||||
),
|
||||
},
|
||||
StepThree: {
|
||||
title: _(msg`Success!`),
|
||||
message: _(msg`Thank you! Your email has been successfully verified.`),
|
||||
},
|
||||
}
|
||||
|
||||
const onSendEmail = async () => {
|
||||
setError('')
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
await agent.com.atproto.server.requestEmailConfirmation()
|
||||
setCurrentStep('StepTwo')
|
||||
} catch (e: unknown) {
|
||||
setError(cleanError(e))
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onVerifyEmail = async () => {
|
||||
setError('')
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
await agent.com.atproto.server.confirmEmail({
|
||||
email: (currentAccount?.email || '').trim(),
|
||||
token: confirmationCode.trim(),
|
||||
})
|
||||
} catch (e: unknown) {
|
||||
setError(cleanError(String(e)))
|
||||
setIsProcessing(false)
|
||||
return
|
||||
}
|
||||
|
||||
setIsProcessing(false)
|
||||
setDidVerify(true)
|
||||
setCurrentStep('StepThree')
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`Verify email dialog`)}
|
||||
style={[
|
||||
gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
|
||||
]}>
|
||||
<Dialog.Close />
|
||||
<View style={[a.gap_xl]}>
|
||||
<View style={[a.gap_sm]}>
|
||||
<Text style={[a.font_heavy, a.text_2xl]}>
|
||||
{uiStrings[currentStep].title}
|
||||
</Text>
|
||||
{error ? (
|
||||
<View style={[a.rounded_sm, a.overflow_hidden]}>
|
||||
<ErrorMessage message={error} />
|
||||
</View>
|
||||
) : null}
|
||||
<Text style={[a.text_md, a.leading_snug]}>
|
||||
{currentStep === 'StepOne' ? (
|
||||
<>
|
||||
<Trans>
|
||||
You'll receive an email at{' '}
|
||||
<Text style={[a.text_md, a.leading_snug, a.font_bold]}>
|
||||
{currentAccount?.email}
|
||||
</Text>{' '}
|
||||
to verify it's you.
|
||||
</Trans>{' '}
|
||||
<InlineLinkText
|
||||
to="#"
|
||||
label={_(msg`Change email address`)}
|
||||
style={[a.text_md, a.leading_snug]}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
control.close(() => {
|
||||
openModal({name: 'change-email'})
|
||||
})
|
||||
return false
|
||||
}}>
|
||||
<Trans>Need to change it?</Trans>
|
||||
</InlineLinkText>
|
||||
</>
|
||||
) : (
|
||||
uiStrings[currentStep].message
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
{currentStep === 'StepTwo' ? (
|
||||
<View>
|
||||
<TextField.LabelText>
|
||||
<Trans>Confirmation Code</Trans>
|
||||
</TextField.LabelText>
|
||||
<TextField.Root>
|
||||
<TextField.Input
|
||||
label={_(msg`Confirmation code`)}
|
||||
placeholder="XXXXX-XXXXX"
|
||||
onChangeText={setConfirmationCode}
|
||||
/>
|
||||
</TextField.Root>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}>
|
||||
{currentStep === 'StepOne' ? (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Send confirmation email`)}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={isProcessing}
|
||||
onPress={onSendEmail}>
|
||||
<ButtonText>
|
||||
<Trans>Send Confirmation</Trans>
|
||||
</ButtonText>
|
||||
{isProcessing ? (
|
||||
<Loader size="sm" style={[{color: 'white'}]} />
|
||||
) : null}
|
||||
</Button>
|
||||
<Button
|
||||
label={_(msg`I Have a Code`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="large"
|
||||
disabled={isProcessing}
|
||||
onPress={() => setCurrentStep('StepTwo')}>
|
||||
<ButtonText>
|
||||
<Trans>I Have a Code</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
) : currentStep === 'StepTwo' ? (
|
||||
<>
|
||||
<Button
|
||||
label={_(msg`Confirm`)}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={isProcessing}
|
||||
onPress={onVerifyEmail}>
|
||||
<ButtonText>
|
||||
<Trans>Confirm</Trans>
|
||||
</ButtonText>
|
||||
{isProcessing ? (
|
||||
<Loader size="sm" style={[{color: 'white'}]} />
|
||||
) : null}
|
||||
</Button>
|
||||
<Button
|
||||
label={_(msg`Resend Email`)}
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="large"
|
||||
disabled={isProcessing}
|
||||
onPress={() => {
|
||||
setConfirmationCode('')
|
||||
setCurrentStep('StepOne')
|
||||
}}>
|
||||
<ButtonText>
|
||||
<Trans>Resend Email</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
) : currentStep === 'StepThree' ? (
|
||||
<Button
|
||||
label={_(msg`Confirm`)}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="large"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Close</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
jsonStringToLex,
|
||||
} from '@atproto/api'
|
||||
|
||||
import {getContentLanguages} from '#/state/preferences/languages'
|
||||
import {
|
||||
getAppLanguageAsContentLanguage,
|
||||
getContentLanguages,
|
||||
} from '#/state/preferences/languages'
|
||||
import {FeedAPI, FeedAPIResponse} from './types'
|
||||
import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils'
|
||||
|
||||
@@ -103,14 +106,27 @@ async function loggedOutFetch({
|
||||
limit: number
|
||||
cursor?: string
|
||||
}) {
|
||||
let contentLangs = getContentLanguages().join(',')
|
||||
let contentLangs = getAppLanguageAsContentLanguage()
|
||||
|
||||
/**
|
||||
* Copied from our root `Agent` class
|
||||
* @see https://github.com/bluesky-social/atproto/blob/60df3fc652b00cdff71dd9235d98a7a4bb828f05/packages/api/src/agent.ts#L120
|
||||
*/
|
||||
const labelersHeader = {
|
||||
'atproto-accept-labelers': BskyAgent.appLabelers
|
||||
.map(l => `${l};redact`)
|
||||
.join(', '),
|
||||
}
|
||||
|
||||
// manually construct fetch call so we can add the `lang` cache-busting param
|
||||
let res = await fetch(
|
||||
`https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
|
||||
cursor ? `&cursor=${cursor}` : ''
|
||||
}&limit=${limit}&lang=${contentLangs}`,
|
||||
{method: 'GET', headers: {'Accept-Language': contentLangs}},
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {'Accept-Language': contentLangs, ...labelersHeader},
|
||||
},
|
||||
)
|
||||
let data = res.ok ? jsonStringToLex(await res.text()) : null
|
||||
if (data?.feed?.length) {
|
||||
@@ -125,7 +141,7 @@ async function loggedOutFetch({
|
||||
`https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${
|
||||
cursor ? `&cursor=${cursor}` : ''
|
||||
}&limit=${limit}`,
|
||||
{method: 'GET', headers: {'Accept-Language': ''}},
|
||||
{method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}},
|
||||
)
|
||||
data = res.ok ? jsonStringToLex(await res.text()) : null
|
||||
if (data?.feed?.length) {
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import {AtUri} from '@atproto/api'
|
||||
|
||||
import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {UsePreferencesQueryResponse} from '#/state/queries/preferences'
|
||||
|
||||
let debugTopics = ''
|
||||
if (isWeb && typeof window !== 'undefined') {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
debugTopics = params.get('debug_topics') ?? ''
|
||||
}
|
||||
|
||||
export function createBskyTopicsHeader(userInterests?: string) {
|
||||
return {
|
||||
'X-Bsky-Topics': userInterests || '',
|
||||
'X-Bsky-Topics': debugTopics || userInterests || '',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {nativeApplicationVersion, nativeBuildVersion} from 'expo-application'
|
||||
export const BUILD_ENV = process.env.EXPO_PUBLIC_ENV
|
||||
export const IS_DEV = process.env.EXPO_PUBLIC_ENV === 'development'
|
||||
export const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
|
||||
export const IS_INTERNAL = IS_DEV || IS_TESTFLIGHT
|
||||
|
||||
// This is the commit hash that the current bundle was made from. The user can see the commit hash in the app's settings
|
||||
// along with the other version info. Useful for debugging/reporting.
|
||||
|
||||
@@ -23,6 +23,14 @@ export const STARTER_PACK_MAX_SIZE = 150
|
||||
// -prf
|
||||
export const JOINED_THIS_WEEK = 150000 // estimate as of 10/9/24
|
||||
|
||||
export const DISCOVER_DEBUG_DIDS: Record<string, true> = {
|
||||
'did:plc:oisofpd7lj26yvgiivf3lxsi': true, // hailey.at
|
||||
'did:plc:fpruhuo22xkm5o7ttr2ktxdo': true, // danabra.mov
|
||||
'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team
|
||||
'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com
|
||||
'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team
|
||||
}
|
||||
|
||||
const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
|
||||
export function FEEDBACK_FORM_URL({
|
||||
email,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type Gate =
|
||||
// Keep this alphabetic please.
|
||||
| 'debug_show_feedcontext'
|
||||
| 'post_feed_lang_window'
|
||||
| 'debug_show_feedcontext' // DISABLED DUE TO EME
|
||||
| 'post_feed_lang_window' // DISABLED DUE TO EME
|
||||
| 'suggested_feeds_interstitial'
|
||||
|
||||
@@ -4,10 +4,10 @@ import {AppState, AppStateStatus} from 'react-native'
|
||||
import {sha256} from 'js-sha256'
|
||||
import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
|
||||
|
||||
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from 'lib/app-info'
|
||||
import {useSession} from '../../state/session'
|
||||
import {timeout} from '../async/timeout'
|
||||
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
|
||||
@@ -89,7 +89,7 @@ export function toClout(n: number | null | undefined): number | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
const DOWNSAMPLE_RATE = 0.95 // 95% likely
|
||||
const DOWNSAMPLE_RATE = 0.99 // 99% likely
|
||||
const DOWNSAMPLED_EVENTS: Set<keyof LogEvents> = new Set([
|
||||
'router:navigate:notifications:sampled',
|
||||
'state:background:sampled',
|
||||
|
||||
@@ -139,6 +139,16 @@ export function getContentLanguages() {
|
||||
return persisted.get('languagePrefs').contentLanguages
|
||||
}
|
||||
|
||||
/**
|
||||
* Be careful with this. It's used for the PWI home screen so that users can
|
||||
* select a UI language and have it apply to the fetched Discover feed.
|
||||
*
|
||||
* We only support BCP-47 two-letter codes here, hence the split.
|
||||
*/
|
||||
export function getAppLanguageAsContentLanguage() {
|
||||
return persisted.get('languagePrefs').appLanguage.split('-')[0]
|
||||
}
|
||||
|
||||
export function toPostLanguages(postLanguage: string): string[] {
|
||||
// filter out empty strings if exist
|
||||
return postLanguage.split(',').filter(Boolean)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {createContext, useContext, useMemo} from 'react'
|
||||
import {BSKY_LABELER_DID, ModerationOpts} from '@atproto/api'
|
||||
import {BskyAgent, ModerationOpts} from '@atproto/api'
|
||||
|
||||
import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences'
|
||||
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
|
||||
@@ -41,12 +41,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
...moderationPrefs,
|
||||
labelers: moderationPrefs.labelers.length
|
||||
? moderationPrefs.labelers
|
||||
: [
|
||||
{
|
||||
did: BSKY_LABELER_DID,
|
||||
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
||||
},
|
||||
],
|
||||
: BskyAgent.appLabelers.map(did => ({
|
||||
did,
|
||||
labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES,
|
||||
})),
|
||||
hiddenPosts: hiddenPosts || [],
|
||||
},
|
||||
labelDefs,
|
||||
|
||||
@@ -28,7 +28,6 @@ import {FeedTuner, FeedTunerFn} from '#/lib/api/feed-manip'
|
||||
import {DISCOVER_FEED_URI} from '#/lib/constants'
|
||||
import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
@@ -123,7 +122,6 @@ export function usePostFeedQuery(
|
||||
params?: FeedParams,
|
||||
opts?: {enabled?: boolean; ignoreFilterFor?: string},
|
||||
) {
|
||||
const gate = useGate()
|
||||
const feedTuners = useFeedTuners(feedDesc)
|
||||
const moderationOpts = useModerationOpts()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
@@ -148,7 +146,10 @@ export function usePostFeedQuery(
|
||||
* unwanted content, we may over-fetch here to try and fill pages by
|
||||
* `MIN_POSTS`.
|
||||
*/
|
||||
const fetchLimit = gate('post_feed_lang_window') ? 100 : MIN_POSTS
|
||||
|
||||
// TEMPORARILY DISABLING GATE TO PREVENT EVENT CONSUMPTION @TODO EME-GATE
|
||||
// const fetchLimit = gate('post_feed_lang_window') ? 100 : MIN_POSTS
|
||||
const fetchLimit = MIN_POSTS
|
||||
|
||||
// Make sure this doesn't invalidate unless really needed.
|
||||
const selectArgs = React.useMemo(
|
||||
|
||||
@@ -4,15 +4,17 @@ import {logger} from '#/logger'
|
||||
import {device} from '#/storage'
|
||||
|
||||
export const BR_LABELER = 'did:plc:ekitcvx7uwnauoqy5oest3hm'
|
||||
export const DE_LABELER = 'did:plc:r55ow3tocux5kafs5dq445fy'
|
||||
export const ADDITIONAL_LABELERS_MAP: {
|
||||
[countryCode: string]: string[]
|
||||
} = {
|
||||
BR: [BR_LABELER],
|
||||
DE: [DE_LABELER],
|
||||
}
|
||||
export const ALL_ADDITIONAL_LABELERS = Object.values(
|
||||
ADDITIONAL_LABELERS_MAP,
|
||||
).flat()
|
||||
export const NON_CONFIGURABLE_LABELERS = [BR_LABELER]
|
||||
export const NON_CONFIGURABLE_LABELERS = [BR_LABELER, DE_LABELER]
|
||||
|
||||
export function isNonConfigurableModerationAuthority(did: string) {
|
||||
return NON_CONFIGURABLE_LABELERS.includes(did)
|
||||
|
||||
@@ -2,6 +2,7 @@ import {AtpSessionData, AtpSessionEvent} from '@atproto/api'
|
||||
import {sha256} from 'js-sha256'
|
||||
import {Statsig} from 'statsig-react-native-expo'
|
||||
|
||||
import {IS_INTERNAL} from '#/lib/app-info'
|
||||
import {Schema} from '../persisted'
|
||||
import {Action, State} from './reducer'
|
||||
import {SessionAccount} from './types'
|
||||
@@ -93,9 +94,13 @@ export function addSessionDebugLog(log: Log) {
|
||||
// Drop these logs for now.
|
||||
return
|
||||
}
|
||||
if (!Statsig.checkGate('debug_session')) {
|
||||
// DISABLING THIS GATE DUE TO EME @TODO EME-GATE
|
||||
if (!IS_INTERNAL) {
|
||||
return
|
||||
}
|
||||
// if (!Statsig.checkGate('debug_session')) {
|
||||
// return
|
||||
// }
|
||||
const messageIndex = nextMessageIndex++
|
||||
const {type, ...content} = log
|
||||
let payload = JSON.stringify(content, replacer)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useState} from 'react'
|
||||
import {Dimensions, TouchableOpacity, View} from 'react-native'
|
||||
import {TouchableOpacity, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
EmbedPlayerParams,
|
||||
parseEmbedPlayerFromUrl,
|
||||
} from '#/lib/strings/embed-player'
|
||||
import {isAndroid, isWeb} from '#/platform/detection'
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {useResolveGifQuery} from '#/state/queries/resolve-link'
|
||||
import {Gif} from '#/state/queries/tenor'
|
||||
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
|
||||
@@ -107,8 +107,7 @@ export function GifAltTextDialogLoaded({
|
||||
control={control}
|
||||
onClose={() => {
|
||||
onSubmit(altTextDraft)
|
||||
}}
|
||||
nativeOptions={{minHeight: Dimensions.get('window').height}}>
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<AltTextInner
|
||||
vendorAltText={vendorAltText}
|
||||
@@ -158,7 +157,7 @@ function AltTextInner({
|
||||
defaultValue={altText}
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
autoFocus={isWeb}
|
||||
autoFocus
|
||||
onKeyPress={({nativeEvent}) => {
|
||||
if (nativeEvent.key === 'Escape') {
|
||||
control.close()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import {Dimensions, ImageStyle, useWindowDimensions, View} from 'react-native'
|
||||
import {ImageStyle, useWindowDimensions, View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -38,8 +38,7 @@ export const ImageAltTextDialog = ({
|
||||
...image,
|
||||
alt: enforceLen(altText, MAX_ALT_TEXT, true),
|
||||
})
|
||||
}}
|
||||
nativeOptions={{minHeight: Dimensions.get('window').height}}>
|
||||
}}>
|
||||
<Dialog.Handle />
|
||||
<ImageAltTextInner
|
||||
control={control}
|
||||
@@ -123,7 +122,7 @@ const ImageAltTextInner = ({
|
||||
defaultValue={altText}
|
||||
multiline
|
||||
numberOfLines={3}
|
||||
autoFocus={isWeb}
|
||||
autoFocus
|
||||
/>
|
||||
</TextField.Root>
|
||||
</View>
|
||||
|
||||
@@ -15,11 +15,10 @@ import {useVideoLibraryPermission} from '#/lib/hooks/usePermissions'
|
||||
import {getHostnameFromUrl} from '#/lib/strings/url-helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {VideoClip_Stroke2_Corner0_Rounded as VideoClipIcon} from '#/components/icons/VideoClip'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
|
||||
@@ -122,24 +121,26 @@ export function SelectVideoBtn({onSelectVideo, disabled, setError}: Props) {
|
||||
|
||||
function VerifyEmailPrompt({control}: {control: Prompt.PromptControlProps}) {
|
||||
const {_} = useLingui()
|
||||
const verifyEmailDialogControl = useDialogControl()
|
||||
const {openModal} = useModalControls()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Prompt.Basic
|
||||
control={control}
|
||||
title={_(msg`Verified email required`)}
|
||||
description={_(
|
||||
msg`To upload videos to Bluesky, you must first verify your email.`,
|
||||
)}
|
||||
confirmButtonCta={_(msg`Verify now`)}
|
||||
confirmButtonColor="primary"
|
||||
onConfirm={() => {
|
||||
verifyEmailDialogControl.open()
|
||||
}}
|
||||
/>
|
||||
<VerifyEmailDialog control={verifyEmailDialogControl} />
|
||||
</>
|
||||
<Prompt.Basic
|
||||
control={control}
|
||||
title={_(msg`Verified email required`)}
|
||||
description={_(
|
||||
msg`To upload videos to Bluesky, you must first verify your email.`,
|
||||
)}
|
||||
confirmButtonCta={_(msg`Verify now`)}
|
||||
confirmButtonColor="primary"
|
||||
onConfirm={() => {
|
||||
control.close(() => {
|
||||
openModal({
|
||||
name: 'verify-email',
|
||||
showReminder: false,
|
||||
})
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ function PostThreadFollowBtnLoaded({
|
||||
onPress={onPress}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary_inverted"
|
||||
color={isFollowing ? 'secondary' : 'secondary_inverted'}
|
||||
style={[a.rounded_full]}>
|
||||
{gtMobile && (
|
||||
<ButtonIcon
|
||||
|
||||
@@ -17,13 +17,13 @@ import {
|
||||
import {msg, plural} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {POST_CTRL_HITSLOP} from '#/lib/constants'
|
||||
import {IS_INTERNAL} from '#/lib/app-info'
|
||||
import {DISCOVER_DEBUG_DIDS, POST_CTRL_HITSLOP} from '#/lib/constants'
|
||||
import {CountWheel} from '#/lib/custom-animations/CountWheel'
|
||||
import {AnimatedLikeIcon} from '#/lib/custom-animations/LikeIcon'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {useGate} from '#/lib/statsig/statsig'
|
||||
import {toShareUrl} from '#/lib/strings/url-helpers'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
@@ -85,7 +85,8 @@ let PostCtrls = ({
|
||||
const {sendInteraction} = useFeedFeedbackContext()
|
||||
const {captureAction} = useProgressGuideControls()
|
||||
const playHaptic = useHaptics()
|
||||
const gate = useGate()
|
||||
const isDiscoverDebugUser =
|
||||
IS_INTERNAL || DISCOVER_DEBUG_DIDS[currentAccount?.did ?? '']
|
||||
const isBlocked = Boolean(
|
||||
post.author.viewer?.blocking ||
|
||||
post.author.viewer?.blockedBy ||
|
||||
@@ -375,7 +376,7 @@ let PostCtrls = ({
|
||||
threadgateRecord={threadgateRecord}
|
||||
/>
|
||||
</View>
|
||||
{gate('debug_show_feedcontext') && feedContext && (
|
||||
{isDiscoverDebugUser && feedContext && (
|
||||
<Pressable
|
||||
accessible={false}
|
||||
style={{
|
||||
|
||||
@@ -33,7 +33,6 @@ export function VideoEmbedInnerWeb({
|
||||
}
|
||||
|
||||
const hlsRef = useHLS({
|
||||
focused,
|
||||
playlist: embed.playlist,
|
||||
setHasSubtitleTrack,
|
||||
setError,
|
||||
@@ -113,14 +112,12 @@ promiseForHls.then(Hls => {
|
||||
})
|
||||
|
||||
function useHLS({
|
||||
focused,
|
||||
playlist,
|
||||
setHasSubtitleTrack,
|
||||
setError,
|
||||
videoRef,
|
||||
setHlsLoading,
|
||||
}: {
|
||||
focused: boolean
|
||||
playlist: string
|
||||
setHasSubtitleTrack: (v: boolean) => void
|
||||
setError: (v: Error | null) => void
|
||||
@@ -155,8 +152,8 @@ function useHLS({
|
||||
if (!hlsRef.current) return
|
||||
const hls = hlsRef.current
|
||||
|
||||
if (focused && hls.nextAutoLevel > 0) {
|
||||
// if the current quality level goes above 0, flush the low quality segments
|
||||
// if the current quality level goes above 0, flush the low quality segments
|
||||
if (hls.nextAutoLevel > 0) {
|
||||
const flushed: HlsTypes.Fragment[] = []
|
||||
|
||||
for (const lowQualFrag of lowQualityFragments) {
|
||||
@@ -179,6 +176,29 @@ function useHLS({
|
||||
},
|
||||
)
|
||||
|
||||
const flushOnLoop = useNonReactiveCallback(() => {
|
||||
if (!Hls) return
|
||||
if (!hlsRef.current) return
|
||||
const hls = hlsRef.current
|
||||
// the above callback will catch most stale frags, but there's a corner case -
|
||||
// if there's only one segment in the video, it won't get flushed because it avoids
|
||||
// flushing the currently active segment. Therefore, we have to catch it when we loop
|
||||
if (
|
||||
hls.nextAutoLevel > 0 &&
|
||||
lowQualityFragments.length === 1 &&
|
||||
lowQualityFragments[0].start === 0
|
||||
) {
|
||||
const lowQualFrag = lowQualityFragments[0]
|
||||
|
||||
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
|
||||
startOffset: lowQualFrag.start,
|
||||
endOffset: lowQualFrag.end,
|
||||
type: 'video',
|
||||
})
|
||||
setLowQualityFragments([])
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef.current) return
|
||||
if (!Hls) return
|
||||
@@ -197,16 +217,14 @@ function useHLS({
|
||||
hls.attachMedia(videoRef.current)
|
||||
hls.loadSource(playlist)
|
||||
|
||||
// initial value, later on it's managed by Controls
|
||||
hls.autoLevelCapping = 0
|
||||
|
||||
// manually loop, so if we've flushed the first buffer it doesn't get confused
|
||||
const abortController = new AbortController()
|
||||
const {signal} = abortController
|
||||
const videoNode = videoRef.current
|
||||
videoNode.addEventListener(
|
||||
'ended',
|
||||
function () {
|
||||
() => {
|
||||
flushOnLoop()
|
||||
videoNode.currentTime = 0
|
||||
videoNode.play()
|
||||
},
|
||||
@@ -248,7 +266,15 @@ function useHLS({
|
||||
hls.destroy()
|
||||
abortController.abort()
|
||||
}
|
||||
}, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange, Hls])
|
||||
}, [
|
||||
playlist,
|
||||
setError,
|
||||
setHasSubtitleTrack,
|
||||
videoRef,
|
||||
handleFragChange,
|
||||
flushOnLoop,
|
||||
Hls,
|
||||
])
|
||||
|
||||
return hlsRef
|
||||
}
|
||||
|
||||
@@ -138,13 +138,10 @@ export function Controls({
|
||||
useEffect(() => {
|
||||
if (!hlsRef.current) return
|
||||
if (focused) {
|
||||
// auto decide quality based on network conditions
|
||||
hlsRef.current.autoLevelCapping = -1
|
||||
// allow 30s of buffering
|
||||
hlsRef.current.config.maxMaxBufferLength = 30
|
||||
} else {
|
||||
// back to what we initially set
|
||||
hlsRef.current.autoLevelCapping = 0
|
||||
hlsRef.current.config.maxMaxBufferLength = 10
|
||||
}
|
||||
}, [hlsRef, focused])
|
||||
|
||||
@@ -503,7 +503,8 @@ let SearchScreenInner = ({
|
||||
)
|
||||
|
||||
const sections = React.useMemo(() => {
|
||||
if (!query) return []
|
||||
if (!queryWithParams) return []
|
||||
const noParams = queryWithParams === query
|
||||
return [
|
||||
{
|
||||
title: _(msg`Top`),
|
||||
@@ -525,22 +526,25 @@ let SearchScreenInner = ({
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
noParams && {
|
||||
title: _(msg`People`),
|
||||
component: (
|
||||
<SearchScreenUserResults query={query} active={activeTab === 2} />
|
||||
),
|
||||
},
|
||||
{
|
||||
noParams && {
|
||||
title: _(msg`Feeds`),
|
||||
component: (
|
||||
<SearchScreenFeedsResults query={query} active={activeTab === 3} />
|
||||
),
|
||||
},
|
||||
]
|
||||
].filter(Boolean) as {
|
||||
title: string
|
||||
component: React.ReactNode
|
||||
}[]
|
||||
}, [_, query, queryWithParams, activeTab])
|
||||
|
||||
return query ? (
|
||||
return queryWithParams ? (
|
||||
<Pager
|
||||
onPageSelected={onPageSelected}
|
||||
renderTabBar={props => (
|
||||
@@ -639,7 +643,7 @@ export function SearchScreen(
|
||||
const {params, query, queryWithParams} = useQueryManager({
|
||||
initialQuery: queryParam,
|
||||
})
|
||||
const showFilters = Boolean(query && !showAutocomplete)
|
||||
const showFilters = Boolean(queryWithParams && !showAutocomplete)
|
||||
/*
|
||||
* Arbitrary sizing, so guess and check, used for sticky header alignment and
|
||||
* sizing.
|
||||
|
||||
@@ -56,7 +56,6 @@ import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateA
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {Email2FAToggle} from './Email2FAToggle'
|
||||
import {ExportCarDialog} from './ExportCarDialog'
|
||||
|
||||
@@ -928,7 +927,7 @@ function EmailConfirmationNotice() {
|
||||
const palInverted = usePalette('inverted')
|
||||
const {_} = useLingui()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const verifyEmailDialogControl = useDialogControl()
|
||||
const {openModal} = useModalControls()
|
||||
|
||||
return (
|
||||
<View style={{marginBottom: 20}}>
|
||||
@@ -960,7 +959,7 @@ function EmailConfirmationNotice() {
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Verify my email`)}
|
||||
accessibilityHint={_(msg`Opens modal for email verification`)}
|
||||
onPress={() => verifyEmailDialogControl.open()}>
|
||||
onPress={() => openModal({name: 'verify-email'})}>
|
||||
<FontAwesomeIcon
|
||||
icon="envelope"
|
||||
color={palInverted.colors.text}
|
||||
@@ -975,7 +974,6 @@ function EmailConfirmationNotice() {
|
||||
<Trans>Protect your account by verifying your email.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<VerifyEmailDialog control={verifyEmailDialogControl} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user