diff --git a/modules/Share-with-Bluesky/Info.plist b/modules/Share-with-Bluesky/Info.plist index 43f46a5e56..d73a9093fc 100644 --- a/modules/Share-with-Bluesky/Info.plist +++ b/modules/Share-with-Bluesky/Info.plist @@ -40,4 +40,4 @@ CFBundleShortVersionString $(MARKETING_VERSION) - + \ No newline at end of file diff --git a/modules/Share-with-Bluesky/ShareViewController.swift b/modules/Share-with-Bluesky/ShareViewController.swift index 63143277a5..46851a0d79 100644 --- a/modules/Share-with-Bluesky/ShareViewController.swift +++ b/modules/Share-with-Bluesky/ShareViewController.swift @@ -23,7 +23,7 @@ class ShareViewController: UIViewController { await self.handleUrl(item: firstAttachment) } else if firstAttachment.hasItemConformingToTypeIdentifier("public.image") { await self.handleImages(items: attachments) - } else if firstAttachment.hasItemConformingToTypeIdentifier("public.video") { + } else if firstAttachment.hasItemConformingToTypeIdentifier("public.movie") { await self.handleVideos(items: attachments) } else { self.completeRequest() @@ -101,13 +101,13 @@ class ShareViewController: UIViewController { private func handleVideos(items: [NSItemProvider]) async { let firstItem = items.first - if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.video") as? URL { + if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.movie") as? URL { let ext = String(dataUri.lastPathComponent.split(separator: ".").last ?? "mp4") if let tempUrl = getTempUrl(ext: ext) { let data = try? Data(contentsOf: dataUri) try? data?.write(to: tempUrl) - if let encoded = dataUri.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + if let encoded = tempUrl.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), let url = URL(string: "\(self.appScheme)://intent/compose?videoUri=\(encoded)") { _ = self.openURL(url) } @@ -150,7 +150,8 @@ class ShareViewController: UIViewController { var responder: UIResponder? = self while responder != nil { if let application = responder as? UIApplication { - return application.perform(#selector(openURL(_:)), with: url) != nil + application.open(url) + return true } responder = responder?.next } diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts index b11ce939f8..46c741bc79 100644 --- a/src/alf/fonts.ts +++ b/src/alf/fonts.ts @@ -45,15 +45,18 @@ export function applyFonts( // '100': 'Inter-Thin', // '200': 'Inter-ExtraLight', // '300': 'Inter-Light', + // '500': 'Inter-Medium', + // '700': 'Inter-Bold', + // '900': 'Inter-Black', '100': 'Inter-Regular', '200': 'Inter-Regular', '300': 'Inter-Regular', '400': 'Inter-Regular', - '500': 'Inter-Medium', + '500': 'Inter-SemiBold', '600': 'Inter-SemiBold', - '700': 'Inter-Bold', + '700': 'Inter-SemiBold', '800': 'Inter-ExtraBold', - '900': 'Inter-Black', + '900': 'Inter-ExtraBold', }[style.fontWeight as string] || 'Inter-Regular' if (style.fontStyle === 'italic') { diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts index 3f30702e85..f3ac17e6af 100644 --- a/src/alf/tokens.ts +++ b/src/alf/tokens.ts @@ -60,6 +60,15 @@ export const fontWeight = { } as const export const gradients = { + primary: { + values: [ + [0, '#054CFF'], + [0.4, '#1085FE'], + [0.6, '#1085FE'], + [1, '#59B9FF'], + ], + hover_value: '#1085FE', + }, sky: { values: [ [0, '#0A7AFF'], diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index 883c06c144..68bb482af2 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -126,10 +126,12 @@ function AccountItem({ - + {profile?.displayName || account.handle}{' '} - {account.handle} + + {account.handle} + {isCurrentAccount ? ( ( secondary: tokens.gradients.sky, secondary_inverted: tokens.gradients.sky, negative: tokens.gradients.sky, + gradient_primary: tokens.gradients.primary, gradient_sky: tokens.gradients.sky, gradient_midnight: tokens.gradients.midnight, gradient_sunrise: tokens.gradients.sunrise, @@ -444,7 +446,7 @@ export const Button = React.forwardRef( [state, variant, color, size, disabled], ) - const flattenedBaseStyles = flatten(baseStyles) + const flattenedBaseStyles = flatten([baseStyles, style]) return ( ( a.align_center, a.justify_center, flattenedBaseStyles, - flatten(style), ...(state.hovered || state.pressed ? [hoverStyles, flatten(hoverStyleProp)] : []), @@ -626,9 +627,9 @@ export function useSharedButtonTextStyles() { } if (size === 'large') { - baseStyles.push(a.text_md, a.leading_tight, web({paddingTop: 1})) + baseStyles.push(a.text_md, a.leading_tight, web({top: -0.4})) } else if (size === 'small') { - baseStyles.push(a.text_sm, a.leading_tight, web({paddingTop: 1})) + baseStyles.push(a.text_sm, a.leading_tight, web({top: -0.4})) } else if (size === 'tiny') { baseStyles.push(a.text_xs, a.leading_tight) } diff --git a/src/components/dialogs/nuxs/TenMillion/Trigger.tsx b/src/components/dialogs/nuxs/TenMillion/Trigger.tsx deleted file mode 100644 index 9616b3b1d3..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/Trigger.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import Svg, {Circle, Path} from 'react-native-svg' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {Nux, useUpsertNuxMutation} from '#/state/queries/nuxs' -import {atoms as a, ViewStyleProp} from '#/alf' -import {Button, ButtonProps} from '#/components/Button' -import * as Dialog from '#/components/Dialog' -import {InlineLinkText} from '#/components/Link' -import * as Prompt from '#/components/Prompt' -import {TenMillion} from './' - -export function Trigger({children}: {children: ButtonProps['children']}) { - const {_} = useLingui() - const {mutate: upsertNux} = useUpsertNuxMutation() - const [show, setShow] = React.useState(false) - const [fallback, setFallback] = React.useState(false) - const control = Prompt.usePromptControl() - - const handleOnPress = () => { - if (!fallback) { - setShow(true) - upsertNux({ - id: Nux.TenMillionDialog, - completed: true, - data: undefined, - }) - } else { - control.open() - } - } - - const onHandleFallback = () => { - setFallback(true) - control.open() - } - - return ( - <> - - - {show && !fallback && ( - setShow(false)} - onFallback={onHandleFallback} - /> - )} - - - - - Bluesky is celebrating 10 million users! - - - - - Together, we're rebuilding the social internet. We're glad you're - here. - - - - - To learn more,{' '} - { - control.close() - }} - style={[a.text_md, a.leading_snug]}> - check out our post. - - - - - - - ) -} - -export function Icon({width, style}: {width: number} & ViewStyleProp) { - return ( - - - - - - - - - - - - - - - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx deleted file mode 100644 index 9c8d47afd0..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function OnePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx deleted file mode 100644 index 1f9467e442..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function PointOnePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx deleted file mode 100644 index 4197be8357..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function TenPercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx deleted file mode 100644 index 0d37971410..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Svg, {Path} from 'react-native-svg' - -export function TwentyFivePercent({fill}: {fill?: string}) { - return ( - - - - ) -} diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx deleted file mode 100644 index 21e775a108..0000000000 --- a/src/components/dialogs/nuxs/TenMillion/index.tsx +++ /dev/null @@ -1,712 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import Animated, {FadeIn} from 'react-native-reanimated' -import ViewShot from 'react-native-view-shot' -import {Image} from 'expo-image' -import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' -import * as MediaLibrary from 'expo-media-library' -import {moderateProfile} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {networkRetry} from '#/lib/async/retry' -import {getCanvas} from '#/lib/canvas' -import {shareUrl} from '#/lib/sharing' -import {logEvent} from '#/lib/statsig/statsig' -import {sanitizeDisplayName} from '#/lib/strings/display-names' -import {sanitizeHandle} from '#/lib/strings/handles' -import {isIOS, isNative} from '#/platform/detection' -import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useProfileQuery} from '#/state/queries/profile' -import {useAgent, useSession} from '#/state/session' -import {useComposerControls} from '#/state/shell' -import {formatCount} from '#/view/com/util/numeric/format' -import * as Toast from '#/view/com/util/Toast' -import {Logomark} from '#/view/icons/Logomark' -import { - atoms as a, - ThemeProvider, - tokens, - useBreakpoints, - useTheme, -} from '#/alf' -import {Button, ButtonIcon, ButtonText} from '#/components/Button' -import * as Dialog from '#/components/Dialog' -import {useNuxDialogContext} from '#/components/dialogs/nuxs' -import {OnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/OnePercent' -import {PointOnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/PointOnePercent' -import {TenPercent} from '#/components/dialogs/nuxs/TenMillion/icons/TenPercent' -import {Divider} from '#/components/Divider' -import {GradientFill} from '#/components/GradientFill' -import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' -import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download' -import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image' -import {Loader} from '#/components/Loader' -import {Text} from '#/components/Typography' - -const DEBUG = false -const RATIO = 8 / 10 -const WIDTH = 2000 -const HEIGHT = WIDTH * RATIO - -function getFontSize(count: number) { - const length = count.toString().length - if (length < 7) { - return 80 - } else if (length < 5) { - return 100 - } else { - return 70 - } -} - -function getPercentBadge(percent: number) { - if (percent <= 0.001) { - return PointOnePercent - } else if (percent <= 0.01) { - return OnePercent - } else if (percent <= 0.1) { - return TenPercent - } - return null -} - -function Frame({children}: {children: React.ReactNode}) { - return ( - - {children} - - ) -} - -export function TenMillion({ - showTimeout, - onClose, - onFallback, -}: { - showTimeout?: number - onClose?: () => void - onFallback?: () => void -}) { - const agent = useAgent() - const nuxDialogs = useNuxDialogContext() - const [userNumber, setUserNumber] = React.useState(0) - const fetching = React.useRef(false) - - React.useEffect(() => { - async function fetchUserNumber() { - const isBlueskyHosted = agent.sessionManager.pdsUrl - ?.toString() - .includes('bsky.network') - - if (isBlueskyHosted && agent.session?.accessJwt) { - const res = await fetch( - `https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`, - { - headers: { - Authorization: `Bearer ${agent.session.accessJwt}`, - }, - }, - ) - - if (!res.ok) { - throw new Error('Network request failed') - } - - const data = await res.json() - - if (data.number && data.number <= 10_000_000) { - setUserNumber(data.number) - } else { - // should be rare - nuxDialogs.dismissActiveNux() - onFallback?.() - } - } else { - nuxDialogs.dismissActiveNux() - onFallback?.() - } - } - - if (!fetching.current) { - fetching.current = true - networkRetry(3, fetchUserNumber).catch(() => { - nuxDialogs.dismissActiveNux() - onFallback?.() - }) - } - }, [ - agent.sessionManager.pdsUrl, - agent.session?.accessJwt, - setUserNumber, - nuxDialogs.dismissActiveNux, - nuxDialogs, - onFallback, - ]) - - return userNumber ? ( - - ) : null -} - -export function TenMillionInner({ - userNumber, - showTimeout, - onClose: onCloseOuter, -}: { - userNumber: number - showTimeout: number - onClose?: () => void -}) { - const t = useTheme() - const lightTheme = useTheme('light') - const {_, i18n} = useLingui() - const control = Dialog.useDialogControl() - const {gtMobile} = useBreakpoints() - const {openComposer} = useComposerControls() - const {currentAccount} = useSession() - const { - isLoading: isProfileLoading, - data: profile, - error: profileError, - } = useProfileQuery({ - did: currentAccount!.did, - }) - const moderationOpts = useModerationOpts() - const nuxDialogs = useNuxDialogContext() - const moderation = React.useMemo(() => { - return profile && moderationOpts - ? moderateProfile(profile, moderationOpts) - : undefined - }, [profile, moderationOpts]) - const [uri, setUri] = React.useState(null) - const percent = userNumber / 10_000_000 - const Badge = getPercentBadge(percent) - const isLoadingData = isProfileLoading || !moderation || !profile - const isLoadingImage = !uri - - const displayName = React.useMemo(() => { - if (!profile || !moderation) return '' - return sanitizeDisplayName( - profile.displayName || sanitizeHandle(profile.handle), - moderation.ui('displayName'), - ) - }, [profile, moderation]) - const handle = React.useMemo(() => { - if (!profile) return '' - return sanitizeHandle(profile.handle, '@') - }, [profile]) - const joinedDate = React.useMemo(() => { - if (!profile || !profile.createdAt) return '' - const date = i18n.date(profile.createdAt, { - month: 'short', - day: 'numeric', - year: 'numeric', - }) - return date - }, [i18n, profile]) - - const error: string = React.useMemo(() => { - if (profileError) { - return _( - msg`Oh no! We weren't able to generate an image for you to share. Rest assured, we're glad you're here 🦋`, - ) - } - return '' - }, [_, profileError]) - - /* - * Opening and closing - */ - React.useEffect(() => { - const timeout = setTimeout(() => { - control.open() - }, showTimeout) - return () => { - clearTimeout(timeout) - } - }, [control, showTimeout]) - const onClose = React.useCallback(() => { - nuxDialogs.dismissActiveNux() - onCloseOuter?.() - }, [nuxDialogs, onCloseOuter]) - - /* - * Actions - */ - const sharePost = React.useCallback(() => { - if (uri) { - control.close(() => { - setTimeout(() => { - logEvent('tmd:post', {}) - openComposer({ - text: _( - msg`Bluesky now has over 10 million users, and I was #${i18n.number( - userNumber, - )}!`, - ), - imageUris: [ - { - uri, - width: WIDTH, - height: HEIGHT, - altText: _( - msg`A virtual certificate with text "Celebrating 10M users on Bluesky, #${i18n.number( - userNumber, - )}, ${displayName} ${handle}, joined on ${joinedDate}"`, - ), - }, - ], - }) - }, 1e3) - }) - } - }, [ - _, - i18n, - control, - openComposer, - uri, - userNumber, - displayName, - handle, - joinedDate, - ]) - const onNativeShare = React.useCallback(() => { - if (uri) { - control.close(() => { - logEvent('tmd:share', {}) - shareUrl(uri) - }) - } - }, [uri, control]) - const onNativeDownload = React.useCallback(async () => { - if (uri) { - const res = await requestMediaLibraryPermissionsAsync() - - if (!res) { - Toast.show( - _( - msg`You must grant access to your photo library to save the image.`, - ), - 'xmark', - ) - return - } - - try { - await MediaLibrary.createAssetAsync(uri) - logEvent('tmd:download', {}) - Toast.show(_(msg`Image saved to your camera roll!`)) - } catch (e: unknown) { - console.log(e) - Toast.show(_(msg`An error occurred while saving the image!`), 'xmark') - return - } - } - }, [_, uri]) - const onWebDownload = React.useCallback(async () => { - if (uri) { - const canvas = await getCanvas(uri) - const imgHref = canvas - .toDataURL('image/png') - .replace('image/png', 'image/octet-stream') - const link = document.createElement('a') - link.setAttribute('download', `Bluesky 10M Users.png`) - link.setAttribute('href', imgHref) - link.click() - logEvent('tmd:download', {}) - } - }, [uri]) - - /* - * Canvas stuff - */ - const imageRef = React.useRef(null) - const captureInProgress = React.useRef(false) - const onCanvasReady = React.useCallback(async () => { - if ( - imageRef.current && - imageRef.current.capture && - !captureInProgress.current - ) { - captureInProgress.current = true - const uri = await imageRef.current.capture() - setUri(uri) - } - }, [setUri]) - const canvas = isLoadingData ? null : ( - - - - - - - - - - - - - - {/* Centered content */} - - - - Celebrating {formatCount(i18n, 10000000)} users - {' '} - 🎉 - - - - # - - - {i18n.number(userNumber)} - - - - {Badge && ( - - - - )} - - {/* End centered content */} - - - - {/* - - */} - - - {displayName} - - - - {handle} - - - {profile.createdAt && ( - - Joined on {joinedDate} - - )} - - - - - - - - - - - - ) - - return ( - - - - - - - {error ? ( - - - (╯°□°)╯︵ ┻━┻ - - - {error} - - - ) : isLoadingData || isLoadingImage ? ( - - ) : ( - - - - )} - - - - {canvas} - - - - Thanks for being one of our first 10 million users. - - - - - Together, we're rebuilding the social internet. We're glad - you're here. - - - - - - - {gtMobile && ( - - Brag a little! - - )} - - - - - - - - - - - ) -} diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx index b93831ad35..e7476972ad 100644 --- a/src/components/dialogs/nuxs/index.tsx +++ b/src/components/dialogs/nuxs/index.tsx @@ -16,10 +16,11 @@ import { import {useProfileQuery} from '#/state/queries/profile' import {SessionAccount, useSession} from '#/state/session' import {useOnboardingState} from '#/state/shell' +/* + * NUXs + */ import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography' import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' -// NUXs -import {TenMillion} from '#/components/dialogs/nuxs/TenMillion' import {IS_DEV} from '#/env' type Context = { @@ -36,14 +37,11 @@ const queuedNuxs: { preferences: UsePreferencesQueryResponse }) => boolean }[] = [ - { - id: Nux.TenMillionDialog, - }, { id: Nux.NeueTypography, enabled(props) { if (props.currentProfile.createdAt) { - if (new Date(props.currentProfile.createdAt) < new Date('2024-09-25')) { + if (new Date(props.currentProfile.createdAt) < new Date('2024-10-01')) { return true } } @@ -176,7 +174,6 @@ function Inner({ return ( - {activeNux === Nux.TenMillionDialog && } {activeNux === Nux.NeueTypography && } ) diff --git a/src/components/hooks/useHeaderOffset.ts b/src/components/hooks/useHeaderOffset.ts index e2290c04fb..5c80e18fe0 100644 --- a/src/components/hooks/useHeaderOffset.ts +++ b/src/components/hooks/useHeaderOffset.ts @@ -9,8 +9,8 @@ export function useHeaderOffset() { return 0 } const navBarHeight = 42 - const tabBarPad = 10 + 10 + 3 // padding + border - const normalLineHeight = 1.2 - const tabBarText = 16 * normalLineHeight * fontScale + const tabBarPad = 10 + 10 + 6 // padding + arbitrary + const normalLineHeight = 20 // matches tab bar + const tabBarText = normalLineHeight * fontScale return navBarHeight + tabBarPad + tabBarText } diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts index fd16387034..ce1d474d31 100644 --- a/src/lib/hooks/useIntentHandler.ts +++ b/src/lib/hooks/useIntentHandler.ts @@ -1,11 +1,11 @@ import React from 'react' import * as Linking from 'expo-linking' -import {logEvent} from 'lib/statsig/statsig' -import {isNative} from 'platform/detection' -import {useSession} from 'state/session' -import {useComposerControls} from 'state/shell' -import {useCloseAllActiveElements} from 'state/util' +import {logEvent} from '#/lib/statsig/statsig' +import {isNative} from '#/platform/detection' +import {useSession} from '#/state/session' +import {useComposerControls} from '#/state/shell' +import {useCloseAllActiveElements} from '#/state/util' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {Referrer} from '../../../modules/expo-bluesky-swiss-army' @@ -52,6 +52,7 @@ export function useIntentHandler() { composeIntent({ text: params.get('text'), imageUrisStr: params.get('imageUris'), + videoUri: params.get('videoUri'), }) return } @@ -80,14 +81,25 @@ export function useComposeIntent() { ({ text, imageUrisStr, + videoUri, }: { text: string | null - imageUrisStr: string | null // unused for right now, will be used later with intents + imageUrisStr: string | null + videoUri: string | null }) => { if (!hasSession) return closeAllActiveElements() + // Whenever a video URI is present, we don't support adding images right now. + if (videoUri) { + openComposer({ + text: text ?? undefined, + videoUri, + }) + return + } + const imageUris = imageUrisStr ?.split(',') .filter(part => { diff --git a/src/logger/index.ts b/src/logger/index.ts index 98635c6a97..7bd812af00 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,10 +1,10 @@ import format from 'date-fns/format' import {nanoid} from 'nanoid/non-secure' +import {isNetworkError} from '#/lib/strings/errors' import {DebugContext} from '#/logger/debugContext' import {add} from '#/logger/logDump' import {Sentry} from '#/logger/sentry' -import {isNetworkError} from 'lib/strings/errors' import * as env from '#/env' export enum LogLevel { diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts index 63a8079623..8166602c8a 100644 --- a/src/state/queries/nuxs/definitions.ts +++ b/src/state/queries/nuxs/definitions.ts @@ -3,23 +3,16 @@ import zod from 'zod' import {BaseNux} from '#/state/queries/nuxs/types' export enum Nux { - TenMillionDialog = 'TenMillionDialog', NeueTypography = 'NeueTypography', } export const nuxNames = new Set(Object.values(Nux)) -export type AppNux = - | BaseNux<{ - id: Nux.TenMillionDialog - data: undefined - }> - | BaseNux<{ - id: Nux.NeueTypography - data: undefined - }> +export type AppNux = BaseNux<{ + id: Nux.NeueTypography + data: undefined +}> export const NuxSchemas: Record | undefined> = { - [Nux.TenMillionDialog]: undefined, [Nux.NeueTypography]: undefined, } diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts index 32d02a63cb..0d77935da9 100644 --- a/src/state/queries/video/video.ts +++ b/src/state/queries/video/video.ts @@ -7,17 +7,17 @@ import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query' import {AbortError} from '#/lib/async/cancelable' import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants' -import {logger} from '#/logger' -import {isWeb} from '#/platform/detection' import { ServerError, UploadLimitError, VideoTooLargeError, -} from 'lib/media/video/errors' -import {CompressedVideo} from 'lib/media/video/types' -import {useCompressVideoMutation} from 'state/queries/video/compress-video' -import {useVideoAgent} from 'state/queries/video/util' -import {useUploadVideoMutation} from 'state/queries/video/video-upload' +} from '#/lib/media/video/errors' +import {CompressedVideo} from '#/lib/media/video/types' +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {useCompressVideoMutation} from '#/state/queries/video/compress-video' +import {useVideoAgent} from '#/state/queries/video/util' +import {useUploadVideoMutation} from '#/state/queries/video/video-upload' type Status = 'idle' | 'compressing' | 'processing' | 'uploading' | 'done' @@ -101,9 +101,11 @@ function reducer(queryClient: QueryClient) { export function useUploadVideo({ setStatus, + initialVideoUri, }: { setStatus: (status: string) => void onSuccess: () => void + initialVideoUri?: string }) { const {_} = useLingui() const queryClient = useQueryClient() @@ -237,21 +239,24 @@ export function useUploadVideo({ signal: state.abortController.signal, }) - const selectVideo = (asset: ImagePickerAsset) => { - // compression step on native converts to mp4, so no need to check there - if (isWeb) { - const mimeType = getMimeType(asset) - if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { - throw new Error(_(msg`Unsupported video type: ${mimeType}`)) + const selectVideo = React.useCallback( + (asset: ImagePickerAsset) => { + // compression step on native converts to mp4, so no need to check there + if (isWeb) { + const mimeType = getMimeType(asset) + if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) { + throw new Error(_(msg`Unsupported video type: ${mimeType}`)) + } } - } - dispatch({ - type: 'SetAsset', - asset, - }) - onSelectVideo(asset) - } + dispatch({ + type: 'SetAsset', + asset, + }) + onSelectVideo(asset) + }, + [_, onSelectVideo], + ) const clearVideo = () => { dispatch({type: 'Reset'}) @@ -265,6 +270,13 @@ export function useUploadVideo({ }) }, []) + // Whenever we receive an initial video uri, we should immediately run compression if necessary + useEffect(() => { + if (initialVideoUri) { + selectVideo({uri: initialVideoUri} as ImagePickerAsset) + } + }, [initialVideoUri, selectVideo]) + return { state, dispatch, diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index 8e12386bd3..1c3aa6a81d 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -38,6 +38,7 @@ export interface ComposerOpts { openEmojiPicker?: (pos: DOMRect | undefined) => void text?: string imageUris?: {uri: string; width: number; height: number; altText?: string}[] + videoUri?: string } type StateContext = ComposerOpts | undefined diff --git a/src/style.css b/src/style.css index 980d92ef77..b0b01ba3eb 100644 --- a/src/style.css +++ b/src/style.css @@ -9,7 +9,7 @@ @font-face { font-family: 'Inter-Regular'; src: local('Inter-Regular'), - url(/assets/fonts/inter/Inter-Regular.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-Regular.otf) format('opentype'); font-weight: 400; font-style: normal; font-display: swap; @@ -17,7 +17,7 @@ @font-face { font-family: 'Inter-Italic'; src: local('Inter-Italic'), - url(/assets/fonts/inter/Inter-Italic.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-Italic.otf) format('opentype'); font-weight: 400; font-style: italic; font-display: swap; @@ -25,14 +25,14 @@ /* @font-face { font-family: "Inter-Medium"; - src: local("Inter-Medium"), url(/assets/fonts/inter/Inter-Medium.otf) format("font/otf"); + src: local("Inter-Medium"), url(/assets/fonts/inter/Inter-Medium.otf) format("opentype"); font-weight: 500; font-style: normal; font-display: swap; } @font-face { font-family: "Inter-MediumItalic"; - src: local("Inter-MediumItalic"), url(/assets/fonts/inter/Inter-MediumItalic.otf) format("font/otf"); + src: local("Inter-MediumItalic"), url(/assets/fonts/inter/Inter-MediumItalic.otf) format("opentype"); font-weight: 500; font-style: italic; font-display: swap; @@ -41,7 +41,7 @@ @font-face { font-family: 'Inter-SemiBold'; src: local('Inter-SemiBold'), - url(/assets/fonts/inter/Inter-SemiBold.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-SemiBold.otf) format('opentype'); font-weight: 600; font-style: normal; font-display: swap; @@ -49,7 +49,7 @@ @font-face { font-family: 'Inter-SemiBoldItalic'; src: local('Inter-SemiBoldItalic'), - url(/assets/fonts/inter/Inter-SemiBoldItalic.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-SemiBoldItalic.otf) format('opentype'); font-weight: 600; font-style: italic; font-display: swap; @@ -57,14 +57,14 @@ /* @font-face { font-family: "Inter-Bold"; - src: local("Inter-Bold"), url(/assets/fonts/inter/Inter-Bold.otf) format("font/otf"); + src: local("Inter-Bold"), url(/assets/fonts/inter/Inter-Bold.otf) format("opentype"); font-weight: 700; font-style: normal; font-display: swap; } @font-face { font-family: "Inter-BoldItalic"; - src: local("Inter-BoldItalic"), url(/assets/fonts/inter/Inter-BoldItalic.otf) format("font/otf"); + src: local("Inter-BoldItalic"), url(/assets/fonts/inter/Inter-BoldItalic.otf) format("opentype"); font-weight: 700; font-style: italic; font-display: swap; @@ -73,7 +73,7 @@ @font-face { font-family: 'Inter-ExtraBold'; src: local('Inter-ExtraBold'), - url(/assets/fonts/inter/Inter-ExtraBold.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-ExtraBold.otf) format('opentype'); font-weight: 800; font-style: normal; font-display: swap; @@ -81,7 +81,7 @@ @font-face { font-family: 'Inter-ExtraBoldItalic'; src: local('Inter-ExtraBoldItalic'), - url(/assets/fonts/inter/Inter-ExtraBoldItalic.otf) format('font/otf'); + url(/assets/fonts/inter/Inter-ExtraBoldItalic.otf) format('opentype'); font-weight: 800; font-style: italic; font-display: swap; @@ -89,14 +89,14 @@ /* @font-face { font-family: "Inter-Black"; - src: local("Inter-Black"), url(/assets/fonts/inter/Inter-Black.otf) format("font/otf"); + src: local("Inter-Black"), url(/assets/fonts/inter/Inter-Black.otf) format("opentype"); font-weight: 900; font-style: normal; font-display: swap; } @font-face { font-family: "Inter-BlackItalic"; - src: local("Inter-BlackItalic"), url(/assets/fonts/inter/Inter-BlackItalic.otf) format("font/otf"); + src: local("Inter-BlackItalic"), url(/assets/fonts/inter/Inter-BlackItalic.otf) format("opentype"); font-weight: 900; font-style: italic; font-display: swap; diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx index a18f17612e..ae18f13905 100644 --- a/src/view/com/auth/SplashScreen.tsx +++ b/src/view/com/auth/SplashScreen.tsx @@ -39,21 +39,21 @@ export const SplashScreen = ({ What's up? - + ) } @@ -440,67 +468,4 @@ const styles = StyleSheet.create({ width: 30, height: 30, }, - - navItemWrapper: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 12, - padding: 12, - borderRadius: 8, - gap: 10, - }, - navItemIconWrapper: { - alignItems: 'center', - justifyContent: 'center', - width: 28, - height: 24, - marginTop: 2, - zIndex: 1, - }, - navItemIconWrapperTablet: { - width: 40, - height: 40, - }, - navItemCount: { - position: 'absolute', - top: 0, - left: 15, - backgroundColor: colors.blue3, - color: colors.white, - fontSize: 12, - fontWeight: '600', - paddingHorizontal: 4, - borderRadius: 6, - }, - navItemCountTablet: { - left: 18, - fontSize: 14, - }, - - newPostBtnContainer: { - flexDirection: 'row', - }, - newPostBtn: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - borderRadius: 24, - paddingTop: 10, - paddingBottom: 12, // visually aligns the text vertically inside the button - paddingLeft: 16, - paddingRight: 18, // looks nicer like this - backgroundColor: colors.blue3, - marginLeft: 12, - marginTop: 20, - marginBottom: 10, - gap: 8, - }, - newPostBtnIconWrapper: { - marginTop: 2, // aligns the icon visually with the text - }, - newPostBtnLabel: { - color: colors.white, - fontSize: 16, - fontWeight: '600', - }, })