Compare commits

...

19 Commits

Author SHA1 Message Date
Samuel Newman dfbfcddef5 Fix video quality for short videos (#5996)
* remove auto level capping

* flush first fragment on loop

(cherry picked from commit d1355d52fc)
2024-11-04 12:33:36 -08:00
dan 66b3f359f8 Hook debugTopics to override X-Bsky-Topics (#6060)
(cherry picked from commit 8d5b551ed6)
2024-11-04 12:33:24 -08:00
Hailey 3be702cbf6 Add dids for discover debug (#6063)
* add dids

* keep is_internal

(cherry picked from commit fb4210b36c)
2024-11-04 12:33:14 -08:00
dan fa55e37775 Change post Follow button color on toggle (#6059)
(cherry picked from commit 2398b84d17)
2024-11-04 12:33:04 -08:00
devin ivy 7dd0171afb bskyweb: fix post text expansion (#6016)
Co-authored-by: Hailey <me@haileyok.com>
(cherry picked from commit d5f8d8224f)
2024-11-04 12:30:32 -08:00
Hailey 89cff31b61 tweak downsample rate to 1% (#5836)
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
(cherry picked from commit 72270fd505)
2024-10-18 12:24:42 -07:00
Brian Olson 3fd8c06138 one http.Client for server instead of new per /ipcc
(cherry picked from commit 22e1914f58)
2024-10-17 14:20:29 -07:00
Eric Bailey 3229511849 Add DE labeler (#5675)
(cherry picked from commit 1c6cee7a0d)
2024-10-17 13:37:38 -07:00
Eric Bailey 754cb25137 Apply labelers and handle language for PWI home (#5816)
(cherry picked from commit 05ca979518)
2024-10-17 13:32:35 -07:00
Hailey 1a992427e5 Disable feed debug gate due to EME (#5815)
(cherry picked from commit e9be8f4574)
2024-10-17 12:55:42 -07:00
Hailey c0456b0da3 Temporarily disable window gate during EME (#5814)
(cherry picked from commit 834beac58c)
2024-10-17 12:55:42 -07:00
Eric Bailey 1557a193d5 Ensure all configured appLabelers are applied for logged out users (#5784)
* Ensure all configured appLabelers are applied for logged out users

* Formatting

(cherry picked from commit 3d9663db1e)
2024-10-16 21:09:26 -05:00
Hailey ac4c68bb65 Merge branch '1.92.1-ota-1' into 1.92.1-ota-2 2024-10-15 11:02:25 -07:00
Eric Bailey c70ffc5737 Support params-only searches (#5767)
(cherry picked from commit fe5eb507ca)
2024-10-15 10:45:18 -07:00
Eric Bailey e98ba06162 Update web font families def (#5749)
(cherry picked from commit db7b875c52)
2024-10-15 10:45:18 -07:00
Eric Bailey 9df712b5a7 Update web font families def (#5749)
(cherry picked from commit db7b875c52)
2024-10-14 08:12:04 -07:00
Hailey 31c8d7d8cb Fix keyboard hiding alt text input after viewing DMs on iOS (#5739)
(cherry picked from commit 432dc867f8)
2024-10-14 08:08:32 -07:00
Hailey 65500294c5 Revert "Update the email verification dialog (#5663)"
This reverts commit 2ebe8f435b.
2024-10-11 17:09:49 -07:00
Hailey 968eece57f update version number 2024-10-11 17:09:23 -07:00
26 changed files with 181 additions and 343 deletions
+3 -1
View File
@@ -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]
+10 -8
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.93.0",
"version": "1.92.1",
"private": true,
"engines": {
"node": ">=18"
+3 -3
View File
@@ -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
}
/**
+15
View File
@@ -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>
)
}
+20 -4
View File
@@ -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) {
+8 -1
View File
@@ -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 || '',
}
}
+1
View File
@@ -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.
+8
View File
@@ -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,
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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',
+10
View File
@@ -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)
+5 -7
View File
@@ -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,
+4 -3
View File
@@ -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)
+6 -1
View File
@@ -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)
+4 -5
View File
@@ -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>
+19 -18
View File
@@ -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
+5 -4
View File
@@ -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])
+10 -6
View File
@@ -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.
+2 -4
View File
@@ -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>
)
}