From 6d4ae3d7194788ad9c66095971dba288a2ba7d38 Mon Sep 17 00:00:00 2001 From: Brian Olson Date: Fri, 6 Sep 2024 16:19:55 -0400 Subject: [PATCH 01/14] quick integration of ipcc service --- bskyweb/cmd/bskyweb/main.go | 6 +++ bskyweb/cmd/bskyweb/server.go | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/bskyweb/cmd/bskyweb/main.go b/bskyweb/cmd/bskyweb/main.go index d9235afdee..306b8f2e5e 100644 --- a/bskyweb/cmd/bskyweb/main.go +++ b/bskyweb/cmd/bskyweb/main.go @@ -60,6 +60,12 @@ func run(args []string) { Value: "", EnvVars: []string{"LINK_HOST"}, }, + &cli.StringFlag{ + Name: "ipcc-host", + Usage: "scheme, hostname, and port of ipcc service", + Value: "https://localhost:8730", + EnvVars: []string{"IPCC_HOST", "IPCC_HOST"}, + }, &cli.BoolFlag{ Name: "debug", Usage: "Enable debug mode", diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 203ed62f4e..afd9247ace 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -1,12 +1,17 @@ package main import ( + "bytes" "context" "crypto/subtle" + "crypto/tls" + "encoding/base64" + "encoding/json" "errors" "fmt" "io/fs" "net/http" + "net/netip" "net/url" "os" "os/signal" @@ -41,6 +46,7 @@ type Config struct { appviewHost string ogcardHost string linkHost string + ipccHost string } func serve(cctx *cli.Context) error { @@ -49,6 +55,7 @@ func serve(cctx *cli.Context) error { appviewHost := cctx.String("appview-host") ogcardHost := cctx.String("ogcard-host") linkHost := cctx.String("link-host") + ipccHost := cctx.String("ipcc-host") basicAuthPassword := cctx.String("basic-auth-password") // Echo @@ -91,6 +98,7 @@ func serve(cctx *cli.Context) error { appviewHost: appviewHost, ogcardHost: ogcardHost, linkHost: linkHost, + ipccHost: ipccHost, }, } @@ -261,6 +269,9 @@ func serve(cctx *cli.Context) error { e.GET("/starter-pack/:handleOrDID/:rkey", server.WebStarterPack) e.GET("/start/:handleOrDID/:rkey", server.WebStarterPack) + // ipcc + e.GET("/ipcc", server.WebIpCC) + if linkHost != "" { linkUrl, err := url.Parse(linkHost) if err != nil { @@ -520,3 +531,61 @@ func (srv *Server) WebProfile(c echo.Context) error { data["requestHost"] = req.Host return c.Render(http.StatusOK, "profile.html", data) } + +type IPCCRequest struct { + IP string `json:"ip"` +} +type IPCCResponse struct { + CC string `json:"countryCode"` +} + +func (srv *Server) WebIpCC(c echo.Context) error { + realIP := c.RealIP() + addr, err := netip.ParseAddr(realIP) + if err != nil { + log.Warnf("could not parse IP %q %s", realIP, err) + return c.JSON(400, IPCCResponse{}) + } + var request []byte + if addr.Is4() { + ip4 := addr.As4() + var dest [8]byte + base64.StdEncoding.Encode(dest[:], ip4[:]) + request, _ = json.Marshal(IPCCRequest{IP: string(dest[:])}) + } else if addr.Is6() { + ip6 := addr.As16() + var dest [24]byte + base64.StdEncoding.Encode(dest[:], ip6[:]) + request, _ = json.Marshal(IPCCRequest{IP: string(dest[:])}) + } + + ipccUrlBuilder, err := url.Parse(srv.cfg.ipccHost) + if err != nil { + log.Errorf("ipcc misconfigured bad url %s", err) + return c.JSON(500, IPCCResponse{}) + } + 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) + if err != nil { + log.Warnf("ipcc backend error %s", err) + return c.JSON(500, IPCCResponse{}) + } + defer response.Body.Close() + dec := json.NewDecoder(response.Body) + var outResponse IPCCResponse + err = dec.Decode(&outResponse) + if err != nil { + log.Warnf("ipcc bad response %s", err) + return c.JSON(500, IPCCResponse{}) + } + return c.JSON(200, outResponse) +} From eec93c4e74fb1c87cb490c5a42c377e1bab41454 Mon Sep 17 00:00:00 2001 From: Brian Olson Date: Fri, 6 Sep 2024 16:34:57 -0400 Subject: [PATCH 02/14] cleanup --- bskyweb/cmd/bskyweb/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bskyweb/cmd/bskyweb/main.go b/bskyweb/cmd/bskyweb/main.go index 306b8f2e5e..3f46c4b00b 100644 --- a/bskyweb/cmd/bskyweb/main.go +++ b/bskyweb/cmd/bskyweb/main.go @@ -64,7 +64,7 @@ func run(args []string) { Name: "ipcc-host", Usage: "scheme, hostname, and port of ipcc service", Value: "https://localhost:8730", - EnvVars: []string{"IPCC_HOST", "IPCC_HOST"}, + EnvVars: []string{"IPCC_HOST"}, }, &cli.BoolFlag{ Name: "debug", From c5faa6034472f241778276a234021fb4eb12f804 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 6 Sep 2024 15:55:23 -0500 Subject: [PATCH 03/14] Redesign play button (#5192) --- src/components/video/PlayButtonIcon.tsx | 35 +++++++++++++++---- src/view/com/composer/videos/VideoPreview.tsx | 2 +- .../com/composer/videos/VideoPreview.web.tsx | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/components/video/PlayButtonIcon.tsx b/src/components/video/PlayButtonIcon.tsx index 5415084ff1..90e93f744b 100644 --- a/src/components/video/PlayButtonIcon.tsx +++ b/src/components/video/PlayButtonIcon.tsx @@ -2,24 +2,47 @@ import React from 'react' import {View} from 'react-native' import {atoms as a, useTheme} from '#/alf' -import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play' +import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play' -export function PlayButtonIcon({size = 44}: {size?: number}) { +export function PlayButtonIcon({size = 36}: {size?: number}) { const t = useTheme() + const bg = t.name === 'light' ? t.palette.contrast_25 : t.palette.contrast_975 + const fg = t.name === 'light' ? t.palette.contrast_975 : t.palette.contrast_25 return ( - + + ) } diff --git a/src/view/com/composer/videos/VideoPreview.tsx b/src/view/com/composer/videos/VideoPreview.tsx index 28b46bae22..60b467d62b 100644 --- a/src/view/com/composer/videos/VideoPreview.tsx +++ b/src/view/com/composer/videos/VideoPreview.tsx @@ -60,7 +60,7 @@ export function VideoPreview({ {autoplayDisabled && ( - + )} diff --git a/src/view/com/composer/videos/VideoPreview.web.tsx b/src/view/com/composer/videos/VideoPreview.web.tsx index 9473be0746..b8fd159506 100644 --- a/src/view/com/composer/videos/VideoPreview.web.tsx +++ b/src/view/com/composer/videos/VideoPreview.web.tsx @@ -83,7 +83,7 @@ export function VideoPreview({ /> {autoplayDisabled && ( - + )} From 00ce95893d9f661a378db002f25def281e433d8b Mon Sep 17 00:00:00 2001 From: Igor Adrov Date: Fri, 6 Sep 2024 23:32:58 +0200 Subject: [PATCH 04/14] Fix starter packs scroll (#5190) --- src/components/StarterPack/Main/ProfilesList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StarterPack/Main/ProfilesList.tsx b/src/components/StarterPack/Main/ProfilesList.tsx index 6174bff021..a5c7cd1b73 100644 --- a/src/components/StarterPack/Main/ProfilesList.tsx +++ b/src/components/StarterPack/Main/ProfilesList.tsx @@ -40,7 +40,7 @@ export const ProfilesList = React.forwardRef( ref, ) { const t = useTheme() - const bottomBarOffset = useBottomBarOffset(200) + const bottomBarOffset = useBottomBarOffset(300) const initialNumToRender = useInitialNumToRender() const {currentAccount} = useSession() const {data, refetch, isError} = useAllListMembersQuery(listUri) From d41a00b373ada4c3566cc5af57f232b34604cc02 Mon Sep 17 00:00:00 2001 From: Jaz Date: Fri, 6 Sep 2024 14:58:19 -0700 Subject: [PATCH 05/14] Trigger a build maybe --- .github/workflows/build-and-push-bskyweb-aws.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 6eb9485b14..01f0813752 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -4,6 +4,8 @@ on: push: branches: - main + pull_request: + branches: [brianolson:ipcc-handler] env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From 6620ee421b125b1ae63a85e29e3e947094fb24c8 Mon Sep 17 00:00:00 2001 From: Jaz Date: Fri, 6 Sep 2024 14:59:02 -0700 Subject: [PATCH 06/14] fix action --- .github/workflows/build-and-push-bskyweb-aws.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index 01f0813752..ddaefbf66f 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -5,7 +5,7 @@ on: branches: - main pull_request: - branches: [brianolson:ipcc-handler] + branches: [main] env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From dc6b04b66fe0d9de0e4fd311172cae3b22fcfeb6 Mon Sep 17 00:00:00 2001 From: Jaz Date: Fri, 6 Sep 2024 15:00:13 -0700 Subject: [PATCH 07/14] nvm --- .github/workflows/build-and-push-bskyweb-aws.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-and-push-bskyweb-aws.yaml b/.github/workflows/build-and-push-bskyweb-aws.yaml index ddaefbf66f..6eb9485b14 100644 --- a/.github/workflows/build-and-push-bskyweb-aws.yaml +++ b/.github/workflows/build-and-push-bskyweb-aws.yaml @@ -4,8 +4,6 @@ on: push: branches: - main - pull_request: - branches: [main] env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} From 7e4f8cabd3971bdf19647d122e3267ab6d1991e8 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 6 Sep 2024 15:01:05 -0700 Subject: [PATCH 08/14] [Video] Handle push/pop on Android for autoplay (#5194) --- package.json | 2 +- .../post-embeds/ActiveVideoNativeContext.tsx | 15 +++++++++-- src/view/com/util/post-embeds/VideoEmbed.tsx | 2 +- .../VideoEmbedInner/VideoEmbedInnerNative.tsx | 4 +++ src/view/shell/index.tsx | 25 ++++++++++++++++++- yarn.lock | 4 +-- 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3f9f0bced7..eff665a649 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "expo-system-ui": "~3.0.4", "expo-task-manager": "~11.8.1", "expo-updates": "~0.25.14", - "expo-video": "https://github.com/bluesky-social/expo/raw/expo-video-1.2.4-patch/packages/expo-video/expo-video-v1.2.4-1.tgz", + "expo-video": "https://github.com/bluesky-social/expo/raw/expo-video-1.2.4-patch/packages/expo-video/expo-video-v1.2.4-2.tgz", "expo-web-browser": "~13.0.3", "fast-text-encoding": "^1.0.6", "history": "^5.3.0", diff --git a/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx b/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx index da8c7a98c4..95fa0bb0ec 100644 --- a/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx +++ b/src/view/com/util/post-embeds/ActiveVideoNativeContext.tsx @@ -1,7 +1,7 @@ import React from 'react' import {useVideoPlayer, VideoPlayer} from 'expo-video' -import {isNative} from '#/platform/detection' +import {isAndroid, isNative} from '#/platform/detection' const Context = React.createContext<{ activeSource: string @@ -26,7 +26,18 @@ export function Provider({children}: {children: React.ReactNode}) { }) const setActiveSourceOuter = (src: string | null, viewId: string | null) => { - setActiveSource(src ? src : '') + // HACK + // expo-video doesn't like it when you try and move a `player` to another `VideoView`. Instead, we need to actually + // unregister that player to let the new screen register it. This is only a problem on Android, so we only need to + // apply it there. + if (src === activeSource && isAndroid) { + setActiveSource('') + setTimeout(() => { + setActiveSource(src ? src : '') + }, 100) + } else { + setActiveSource(src ? src : '') + } setActiveViewId(viewId ? viewId : '') } diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index e5457555ba..9c3a34dda8 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -71,7 +71,7 @@ function InnerWrapper({embed}: Props) { const [playerStatus, setPlayerStatus] = useState< VideoPlayerStatus | 'paused' - >(player.playing ? 'readyToPlay' : 'paused') + >('paused') const [isMuted, setIsMuted] = useState(player.muted) const [isFullscreen, setIsFullscreen] = React.useState(false) const [timeRemaining, setTimeRemaining] = React.useState(0) diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx index 31e8630382..de9a2c74c2 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -8,6 +8,7 @@ import {useLingui} from '@lingui/react' import {HITSLOP_30} from '#/lib/constants' import {clamp} from '#/lib/numbers' +import {isAndroid} from 'platform/detection' import {useActiveVideoNative} from 'view/com/util/post-embeds/ActiveVideoNativeContext' import {atoms as a, useTheme} from '#/alf' import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' @@ -61,6 +62,9 @@ export function VideoEmbedInnerNative({ PlatformInfo.setAudioActive(true) player.muted = false setIsFullscreen(true) + if (isAndroid) { + player.play() + } }} onFullscreenExit={() => { PlatformInfo.setAudioCategory(AudioCategory.Ambient) diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 7d080e57b1..aed92cbb7a 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -11,7 +11,7 @@ import Animated from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as NavigationBar from 'expo-navigation-bar' import {StatusBar} from 'expo-status-bar' -import {useNavigationState} from '@react-navigation/native' +import {useNavigation, useNavigationState} from '@react-navigation/native' import {useSession} from '#/state/session' import { @@ -20,6 +20,7 @@ import { useSetDrawerOpen, } from '#/state/shell' import {useCloseAnyActiveElement} from '#/state/util' +import {useDedupe} from 'lib/hooks/useDedupe' import {useNotificationsHandler} from 'lib/hooks/useNotificationHandler' import {usePalette} from 'lib/hooks/usePalette' import {useNotificationsRegistration} from 'lib/notifications/notifications' @@ -33,6 +34,7 @@ import {ErrorBoundary} from 'view/com/util/ErrorBoundary' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {SigninDialog} from '#/components/dialogs/Signin' import {Outlet as PortalOutlet} from '#/components/Portal' +import {updateActiveViewAsync} from '../../../modules/expo-bluesky-swiss-army/src/VisibilityView' import {RoutesContainer, TabsNavigator} from '../../Navigation' import {Composer} from './Composer' import {DrawerContent} from './Drawer' @@ -76,6 +78,27 @@ function ShellInner() { } }, [closeAnyActiveElement]) + // HACK + // expo-video doesn't like it when you try and move a `player` to another `VideoView`. Instead, we need to actually + // unregister that player to let the new screen register it. This is only a problem on Android, so we only need to + // apply it there. + // The `state` event should only fire whenever we push or pop to a screen, and should not fire consecutively quickly. + // To be certain though, we will also dedupe these calls. + const navigation = useNavigation() + const dedupe = useDedupe(1000) + React.useEffect(() => { + if (!isAndroid) return + const onFocusOrBlur = () => { + setTimeout(() => { + dedupe(updateActiveViewAsync) + }, 500) + } + navigation.addListener('state', onFocusOrBlur) + return () => { + navigation.removeListener('state', onFocusOrBlur) + } + }, [dedupe, navigation]) + return ( <> Date: Fri, 6 Sep 2024 17:58:47 -0500 Subject: [PATCH 09/14] Add emoji picker to chat composer (#5196) Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Adrov Igor --- .../Messages/Conversation/MessageInput.tsx | 2 + .../Conversation/MessageInput.web.tsx | 66 ++++++++++++++++++- .../Messages/Conversation/MessagesList.tsx | 21 +++++- src/state/shell/composer.tsx | 2 +- src/view/com/composer/Composer.tsx | 6 +- .../com/composer/text-input/TextInput.web.tsx | 4 +- .../text-input/textInputWebEmitter.ts | 3 + .../text-input/web/EmojiPicker.web.tsx | 27 ++++++-- src/view/shell/Composer.web.tsx | 2 +- 9 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 src/view/com/composer/text-input/textInputWebEmitter.ts diff --git a/src/screens/Messages/Conversation/MessageInput.tsx b/src/screens/Messages/Conversation/MessageInput.tsx index dc63a869a6..674edc41eb 100644 --- a/src/screens/Messages/Conversation/MessageInput.tsx +++ b/src/screens/Messages/Conversation/MessageInput.tsx @@ -23,6 +23,7 @@ import { useSaveMessageDraft, } from '#/state/messages/message-drafts' import {isIOS} from 'platform/detection' +import {EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker.web' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' import {useSharedInputStyles} from '#/components/forms/TextField' @@ -41,6 +42,7 @@ export function MessageInput({ hasEmbed: boolean setEmbed: (embedUrl: string | undefined) => void children?: React.ReactNode + openEmojiPicker?: (pos: EmojiPickerPosition) => void }) { const {_} = useLingui() const t = useTheme() diff --git a/src/screens/Messages/Conversation/MessageInput.web.tsx b/src/screens/Messages/Conversation/MessageInput.web.tsx index a4a8a78522..0b7e479209 100644 --- a/src/screens/Messages/Conversation/MessageInput.web.tsx +++ b/src/screens/Messages/Conversation/MessageInput.web.tsx @@ -12,9 +12,16 @@ import { } from '#/state/messages/message-drafts' import {isSafari, isTouchDevice} from 'lib/browser' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' +import { + Emoji, + EmojiPickerPosition, +} from '#/view/com/composer/text-input/web/EmojiPicker.web' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' import {useSharedInputStyles} from '#/components/forms/TextField' +import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane' import {useExtractEmbedFromFacets} from './MessageInputEmbed' @@ -23,11 +30,13 @@ export function MessageInput({ hasEmbed, setEmbed, children, + openEmojiPicker, }: { onSendMessage: (message: string) => void hasEmbed: boolean setEmbed: (embedUrl: string | undefined) => void children?: React.ReactNode + openEmojiPicker?: (pos: EmojiPickerPosition) => void }) { const {isTabletOrDesktop} = useWebMediaQueries() const {_} = useLingui() @@ -40,6 +49,7 @@ export function MessageInput({ const [isFocused, setIsFocused] = React.useState(false) const [isHovered, setIsHovered] = React.useState(false) const [textAreaHeight, setTextAreaHeight] = React.useState(38) + const textAreaRef = React.useRef(null) const onSubmit = React.useCallback(() => { if (!hasEmbed && message.trim() === '') { @@ -94,6 +104,23 @@ export function MessageInput({ [], ) + const onEmojiInserted = React.useCallback( + (emoji: Emoji) => { + const position = textAreaRef.current?.selectionStart ?? 0 + setMessage( + message => + message.slice(0, position) + emoji.native + message.slice(position), + ) + }, + [setMessage], + ) + React.useEffect(() => { + textInputWebEmitter.addListener('emoji-inserted', onEmojiInserted) + return () => { + textInputWebEmitter.removeListener('emoji-inserted', onEmojiInserted) + } + }, [onEmojiInserted]) + useSaveMessageDraft(message) useExtractEmbedFromFacets(message, setEmbed) @@ -106,7 +133,7 @@ export function MessageInput({ t.atoms.bg_contrast_25, { paddingRight: a.p_sm.padding - 2, - paddingLeft: a.p_md.padding - 2, + paddingLeft: a.p_sm.padding - 2, borderWidth: 1, borderRadius: 23, borderColor: 'transparent', @@ -118,7 +145,44 @@ export function MessageInput({ // @ts-expect-error web only onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> + ({ + isOpen: false, + pos: {top: 0, left: 0, right: 0, bottom: 0}, + }) + // We need to keep track of when the scroll offset is at the bottom of the list to know when to scroll as new items // are added to the list. For example, if the user is scrolled up to 1iew older messages, we don't want to scroll to // the bottom. @@ -422,13 +432,22 @@ export function MessagesList({ + setEmbed={setEmbed} + openEmojiPicker={pos => setEmojiPickerState({isOpen: true, pos})}> )} + {isWeb && ( + setEmojiPickerState(prev => ({...prev, isOpen: false}))} + /> + )} + {newMessagesPill.show && } ) diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx index 74802a9930..612388ff86 100644 --- a/src/state/shell/composer.tsx +++ b/src/state/shell/composer.tsx @@ -34,7 +34,7 @@ export interface ComposerOpts { quote?: ComposerOptsQuote quoteCount?: number mention?: string // handle of user to mention - openPicker?: (pos: DOMRect | undefined) => void + openEmojiPicker?: (pos: DOMRect | undefined) => void text?: string imageUris?: {uri: string; width: number; height: number}[] } diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 8ae92b0181..3c7868ad2d 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -133,7 +133,7 @@ export const ComposePost = observer(function ComposePost({ quote: initQuote, quoteCount, mention: initMention, - openPicker, + openEmojiPicker, text: initText, imageUris: initImageUris, cancelRef, @@ -520,8 +520,8 @@ export const ComposePost = observer(function ComposePost({ gallery.size > 0 || Boolean(extLink) || Boolean(videoUploadState.video) const onEmojiButtonPress = useCallback(() => { - openPicker?.(textInput.current?.getCursorPosition()) - }, [openPicker]) + openEmojiPicker?.(textInput.current?.getCursorPosition()) + }, [openEmojiPicker]) const focusTextInput = useCallback(() => { textInput.current?.focus() diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 3c4aaf7388..c477ada065 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -12,12 +12,12 @@ import {Placeholder} from '@tiptap/extension-placeholder' import {Text as TiptapText} from '@tiptap/extension-text' import {generateJSON} from '@tiptap/html' import {EditorContent, JSONContent, useEditor} from '@tiptap/react' -import EventEmitter from 'eventemitter3' import {usePalette} from '#/lib/hooks/usePalette' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' import {blobToDataUri, isUriImage} from 'lib/media/util' +import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import { LinkFacetMatch, suggestLinkCardUri, @@ -46,8 +46,6 @@ interface TextInputProps { onError: (err: string) => void } -export const textInputWebEmitter = new EventEmitter() - export const TextInput = React.forwardRef(function TextInputImpl( { richtext, diff --git a/src/view/com/composer/text-input/textInputWebEmitter.ts b/src/view/com/composer/text-input/textInputWebEmitter.ts new file mode 100644 index 0000000000..fb037cac2e --- /dev/null +++ b/src/view/com/composer/text-input/textInputWebEmitter.ts @@ -0,0 +1,3 @@ +import EventEmitter from 'eventemitter3' + +export const textInputWebEmitter = new EventEmitter() diff --git a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx index 1f4178f7f8..ad3bb30eca 100644 --- a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx +++ b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx @@ -7,8 +7,8 @@ import { } from 'react-native' import Picker from '@emoji-mart/react' +import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a} from '#/alf' -import {textInputWebEmitter} from '../TextInput.web' const HEIGHT_OFFSET = 40 const WIDTH_OFFSET = 100 @@ -26,22 +26,41 @@ export type Emoji = { unified: string } +export interface EmojiPickerPosition { + top: number + left: number + right: number + bottom: number +} + export interface EmojiPickerState { isOpen: boolean - pos: {top: number; left: number; right: number; bottom: number} + pos: EmojiPickerPosition } interface IProps { state: EmojiPickerState close: () => void + /** + * If `true`, overrides position and ensures picker is pinned to the top of + * the target element. + */ + pinToTop?: boolean } -export function EmojiPicker({state, close}: IProps) { +export function EmojiPicker({state, close, pinToTop}: IProps) { const {height, width} = useWindowDimensions() const isShiftDown = React.useRef(false) const position = React.useMemo(() => { + if (pinToTop) { + return { + top: state.pos.top - PICKER_HEIGHT + HEIGHT_OFFSET - 10, + left: state.pos.left, + } + } + const fitsBelow = state.pos.top + PICKER_HEIGHT < height const fitsAbove = PICKER_HEIGHT < state.pos.top const placeOnLeft = PICKER_WIDTH < state.pos.left @@ -64,7 +83,7 @@ export function EmojiPicker({state, close}: IProps) { : undefined, } } - }, [state.pos, height, width]) + }, [state.pos, height, width, pinToTop]) React.useEffect(() => { if (!state.isOpen) return diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 5d80dc422b..42696139e0 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -61,7 +61,7 @@ export function Composer({}: {winHeight: number}) { quoteCount={state?.quoteCount} onPost={state.onPost} mention={state.mention} - openPicker={onOpenPicker} + openEmojiPicker={onOpenPicker} text={state.text} /> From 275f2bb00418f3c866b0b68f92145f71c028e00c Mon Sep 17 00:00:00 2001 From: nicofercavv <70589176+nicofercavv-dev@users.noreply.github.com> Date: Fri, 6 Sep 2024 20:18:31 -0300 Subject: [PATCH 10/14] Add cursor pointer to 'New post' button (#5109) --- src/view/com/util/fab/FABInner.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/com/util/fab/FABInner.tsx b/src/view/com/util/fab/FABInner.tsx index e6fb0ad465..ee8e1f47a2 100644 --- a/src/view/com/util/fab/FABInner.tsx +++ b/src/view/com/util/fab/FABInner.tsx @@ -79,6 +79,7 @@ const styles = StyleSheet.create({ // @ts-ignore web-only position: isWeb ? 'fixed' : 'absolute', zIndex: 1, + cursor: 'pointer', }, inner: { justifyContent: 'center', From adef9cff10eb8cbd5227c1fde0f94068fb6987f6 Mon Sep 17 00:00:00 2001 From: jlca Date: Fri, 6 Sep 2024 23:29:36 -0600 Subject: [PATCH 11/14] fix: remove duplicate style `rounded_sm` (#5201) --- src/view/com/util/Toast.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx index f7c6bc2c91..51e76bdc39 100644 --- a/src/view/com/util/Toast.tsx +++ b/src/view/com/util/Toast.tsx @@ -59,7 +59,6 @@ function Toast({ a.flex_1, t.atoms.bg, a.shadow_lg, - a.rounded_sm, t.atoms.border_contrast_medium, a.rounded_sm, a.px_md, From c8be9b78c6abb1ca98a2e4c9342e314b19d2cb7c Mon Sep 17 00:00:00 2001 From: Hailey Date: Sat, 7 Sep 2024 04:13:51 -0700 Subject: [PATCH 12/14] [Statsig] Add more events to downsample, increase downsample rate (#5198) * add some events for sampling * include downsample rate in metadata * fix metadata logic * uncomment debug --- src/Navigation.tsx | 9 ++++----- src/components/ProfileCard.tsx | 4 ++-- src/components/hooks/useFollowMethods.ts | 4 ++-- src/lib/statsig/events.ts | 14 +++++++------- src/lib/statsig/statsig.tsx | 17 ++++++++++++++--- src/state/queries/post.ts | 24 ++++++++++++------------ src/state/queries/profile.ts | 12 ++++++------ 7 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 0bf0e9f93e..2beba4f9dc 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -661,16 +661,15 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { linking={LINKING} theme={theme} onStateChange={() => { - logEvent('router:navigate:sampled', { - from: prevLoggedRouteName.current, - }) - prevLoggedRouteName.current = getCurrentRouteName() + const routeName = getCurrentRouteName() + if (routeName === 'Notifications') { + logEvent('router:navigate:notifications:sampled', {}) + } }} onReady={() => { attachRouteToLogEvents(getCurrentRouteName) logModuleInitTime() onReady() - logEvent('router:navigate:sampled', {}) }}> {children} diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index a263d19461..6f6d68049a 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -276,8 +276,8 @@ export function DescriptionPlaceholder() { export type FollowButtonProps = { profile: AppBskyActorDefs.ProfileViewBasic moderationOpts: ModerationOpts - logContext: LogEvents['profile:follow']['logContext'] & - LogEvents['profile:unfollow']['logContext'] + logContext: LogEvents['profile:follow:sampled']['logContext'] & + LogEvents['profile:unfollow:sampled']['logContext'] } & Partial export function FollowButton(props: FollowButtonProps) { diff --git a/src/components/hooks/useFollowMethods.ts b/src/components/hooks/useFollowMethods.ts index d67c3690f9..31a1e43daa 100644 --- a/src/components/hooks/useFollowMethods.ts +++ b/src/components/hooks/useFollowMethods.ts @@ -15,8 +15,8 @@ export function useFollowMethods({ logContext, }: { profile: Shadow - logContext: LogEvents['profile:follow']['logContext'] & - LogEvents['profile:unfollow']['logContext'] + logContext: LogEvents['profile:follow:sampled']['logContext'] & + LogEvents['profile:unfollow:sampled']['logContext'] }) { const {_} = useLingui() const requireAuth = useRequireAuth() diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts index 4768bdc238..1871894902 100644 --- a/src/lib/statsig/events.ts +++ b/src/lib/statsig/events.ts @@ -25,7 +25,7 @@ export type LogEvents = { secondsActive: number } 'state:foreground:sampled': {} - 'router:navigate:sampled': {} + 'router:navigate:notifications:sampled': {} 'deepLink:referrerReceived': { to: string referrer: string @@ -127,25 +127,25 @@ export type LogEvents = { langs: string logContext: 'Composer' } - 'post:like': { + 'post:like:sampled': { doesLikerFollowPoster: boolean | undefined doesPosterFollowLiker: boolean | undefined likerClout: number | undefined postClout: number | undefined logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } - 'post:repost': { + 'post:repost:sampled': { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } - 'post:unlike': { + 'post:unlike:sampled': { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } - 'post:unrepost': { + 'post:unrepost:sampled': { logContext: 'FeedItem' | 'PostThreadItem' | 'Post' } 'post:mute': {} 'post:unmute': {} - 'profile:follow': { + 'profile:follow:sampled': { didBecomeMutual: boolean | undefined followeeClout: number | undefined followerClout: number | undefined @@ -162,7 +162,7 @@ export type LogEvents = { | 'FeedInterstitial' | 'ProfileHeaderSuggestedFollows' } - 'profile:unfollow': { + 'profile:unfollow:sampled': { logContext: | 'RecommendedFollowsItem' | 'PostThreadItem' diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index 7d86f4078c..c50bfeb862 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -89,8 +89,9 @@ export function toClout(n: number | null | undefined): number | undefined { } } +const DOWNSAMPLE_RATE = 0.95 // 95% likely const DOWNSAMPLED_EVENTS: Set = new Set([ - 'router:navigate:sampled', + 'router:navigate:notifications:sampled', 'state:background:sampled', 'state:foreground:sampled', 'home:feedDisplayed:sampled', @@ -99,8 +100,14 @@ const DOWNSAMPLED_EVENTS: Set = new Set([ 'discover:clickthrough:sampled', 'discover:engaged:sampled', 'discover:seen:sampled', + 'post:like:sampled', + 'post:unlike:sampled', + 'post:repost:sampled', + 'post:unrepost:sampled', + 'profile:follow:sampled', + 'profile:unfollow:sampled', ]) -const isDownsampledSession = Math.random() < 0.9 // 90% likely +const isDownsampledSession = Math.random() < DOWNSAMPLE_RATE export function logEvent( eventName: E & string, @@ -117,12 +124,16 @@ export function logEvent( ) } - if (isDownsampledSession && DOWNSAMPLED_EVENTS.has(eventName)) { + const isDownsampledEvent = DOWNSAMPLED_EVENTS.has(eventName) + if (isDownsampledSession && isDownsampledEvent) { return } const fullMetadata = { ...rawMetadata, } as Record // Statsig typings are unnecessarily strict here. + if (isDownsampledEvent) { + fullMetadata.downsampleRate = DOWNSAMPLE_RATE.toString() + } fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' if (Statsig.initializeCalled()) { Statsig.logEvent(eventName, null, fullMetadata) diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index 197903bee5..982d224aee 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -99,8 +99,8 @@ export function useGetPosts() { export function usePostLikeMutationQueue( post: Shadow, - logContext: LogEvents['post:like']['logContext'] & - LogEvents['post:unlike']['logContext'], + logContext: LogEvents['post:like:sampled']['logContext'] & + LogEvents['post:unlike:sampled']['logContext'], ) { const queryClient = useQueryClient() const postUri = post.uri @@ -158,7 +158,7 @@ export function usePostLikeMutationQueue( } function usePostLikeMutation( - logContext: LogEvents['post:like']['logContext'], + logContext: LogEvents['post:like:sampled']['logContext'], post: Shadow, ) { const {currentAccount} = useSession() @@ -175,7 +175,7 @@ function usePostLikeMutation( if (currentAccount) { ownProfile = findProfileQueryData(queryClient, currentAccount.did) } - logEvent('post:like', { + logEvent('post:like:sampled', { logContext, doesPosterFollowLiker: postAuthor.viewer ? Boolean(postAuthor.viewer.followedBy) @@ -200,12 +200,12 @@ function usePostLikeMutation( } function usePostUnlikeMutation( - logContext: LogEvents['post:unlike']['logContext'], + logContext: LogEvents['post:unlike:sampled']['logContext'], ) { const agent = useAgent() return useMutation({ mutationFn: ({likeUri}) => { - logEvent('post:unlike', {logContext}) + logEvent('post:unlike:sampled', {logContext}) return agent.deleteLike(likeUri) }, onSuccess() { @@ -216,8 +216,8 @@ function usePostUnlikeMutation( export function usePostRepostMutationQueue( post: Shadow, - logContext: LogEvents['post:repost']['logContext'] & - LogEvents['post:unrepost']['logContext'], + logContext: LogEvents['post:repost:sampled']['logContext'] & + LogEvents['post:unrepost:sampled']['logContext'], ) { const queryClient = useQueryClient() const postUri = post.uri @@ -273,7 +273,7 @@ export function usePostRepostMutationQueue( } function usePostRepostMutation( - logContext: LogEvents['post:repost']['logContext'], + logContext: LogEvents['post:repost:sampled']['logContext'], ) { const agent = useAgent() return useMutation< @@ -282,7 +282,7 @@ function usePostRepostMutation( {uri: string; cid: string} // the post's uri and cid >({ mutationFn: post => { - logEvent('post:repost', {logContext}) + logEvent('post:repost:sampled', {logContext}) return agent.repost(post.uri, post.cid) }, onSuccess() { @@ -292,12 +292,12 @@ function usePostRepostMutation( } function usePostUnrepostMutation( - logContext: LogEvents['post:unrepost']['logContext'], + logContext: LogEvents['post:unrepost:sampled']['logContext'], ) { const agent = useAgent() return useMutation({ mutationFn: ({repostUri}) => { - logEvent('post:unrepost', {logContext}) + logEvent('post:unrepost:sampled', {logContext}) return agent.deleteRepost(repostUri) }, onSuccess() { diff --git a/src/state/queries/profile.ts b/src/state/queries/profile.ts index 6682cf3c89..532b005cf4 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -219,8 +219,8 @@ export function useProfileUpdateMutation() { export function useProfileFollowMutationQueue( profile: Shadow, - logContext: LogEvents['profile:follow']['logContext'] & - LogEvents['profile:unfollow']['logContext'], + logContext: LogEvents['profile:follow:sampled']['logContext'] & + LogEvents['profile:follow:sampled']['logContext'], ) { const agent = useAgent() const queryClient = useQueryClient() @@ -291,7 +291,7 @@ export function useProfileFollowMutationQueue( } function useProfileFollowMutation( - logContext: LogEvents['profile:follow']['logContext'], + logContext: LogEvents['profile:follow:sampled']['logContext'], profile: Shadow, ) { const {currentAccount} = useSession() @@ -306,7 +306,7 @@ function useProfileFollowMutation( ownProfile = findProfileQueryData(queryClient, currentAccount.did) } captureAction(ProgressGuideAction.Follow) - logEvent('profile:follow', { + logEvent('profile:follow:sampled', { logContext, didBecomeMutual: profile.viewer ? Boolean(profile.viewer.followedBy) @@ -323,12 +323,12 @@ function useProfileFollowMutation( } function useProfileUnfollowMutation( - logContext: LogEvents['profile:unfollow']['logContext'], + logContext: LogEvents['profile:unfollow:sampled']['logContext'], ) { const agent = useAgent() return useMutation({ mutationFn: async ({followUri}) => { - logEvent('profile:unfollow', {logContext}) + logEvent('profile:unfollow:sampled', {logContext}) track('Profile:Unfollow', {username: followUri}) return await agent.deleteFollow(followUri) }, From 7d7431d14e52d94cce51941013c8a5fe5665adb1 Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 7 Sep 2024 17:07:30 +0200 Subject: [PATCH 13/14] Set fixed_bottom_bar to true (#5203) --- src/lib/statsig/gates.ts | 1 - src/view/com/util/MainScrollProvider.tsx | 19 ++----------- src/view/screens/Home.tsx | 34 ++---------------------- 3 files changed, 4 insertions(+), 50 deletions(-) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index be40548adf..0c44b419e1 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,7 +1,6 @@ export type Gate = // Keep this alphabetic please. | 'debug_show_feedcontext' - | 'fixed_bottom_bar' | 'onboarding_minimum_interests' | 'suggested_feeds_interstitial' | 'show_follow_suggestions_in_profile' diff --git a/src/view/com/util/MainScrollProvider.tsx b/src/view/com/util/MainScrollProvider.tsx index 3163d85445..c87ee209ec 100644 --- a/src/view/com/util/MainScrollProvider.tsx +++ b/src/view/com/util/MainScrollProvider.tsx @@ -9,7 +9,6 @@ import { import EventEmitter from 'eventemitter3' import {ScrollProvider} from '#/lib/ScrollContext' -import {useGate} from '#/lib/statsig/statsig' import {useMinimalShellMode} from '#/state/shell' import {useShellLayout} from '#/state/shell/shell-layout' import {isNative, isWeb} from 'platform/detection' @@ -23,12 +22,10 @@ function clamp(num: number, min: number, max: number) { export function MainScrollProvider({children}: {children: React.ReactNode}) { const {headerHeight} = useShellLayout() - const {headerMode, footerMode} = useMinimalShellMode() + const {headerMode} = useMinimalShellMode() const startDragOffset = useSharedValue(null) const startMode = useSharedValue(null) const didJustRestoreScroll = useSharedValue(false) - const gate = useGate() - const isFixedBottomBar = gate('fixed_bottom_bar') const setMode = React.useCallback( (v: boolean) => { @@ -37,14 +34,8 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { headerMode.value = withSpring(v ? 1 : 0, { overshootClamping: true, }) - if (!isFixedBottomBar) { - cancelAnimation(footerMode) - footerMode.value = withSpring(v ? 1 : 0, { - overshootClamping: true, - }) - } }, - [headerMode, footerMode, isFixedBottomBar], + [headerMode], ) useEffect(() => { @@ -147,10 +138,6 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { // Cancel any any existing animation cancelAnimation(headerMode) headerMode.value = newValue - if (!isFixedBottomBar) { - cancelAnimation(footerMode) - footerMode.value = newValue - } } } else { if (didJustRestoreScroll.value) { @@ -173,12 +160,10 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) { [ headerHeight, headerMode, - footerMode, setMode, startDragOffset, startMode, didJustRestoreScroll, - isFixedBottomBar, ], ) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index fb487ad6bf..c790a815b6 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,24 +1,18 @@ import React from 'react' -import {ActivityIndicator, AppState, StyleSheet, View} from 'react-native' +import {ActivityIndicator, StyleSheet, View} from 'react-native' import {useFocusEffect} from '@react-navigation/native' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useSetTitle} from '#/lib/hooks/useSetTitle' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {logEvent, LogEvents} from '#/lib/statsig/statsig' -import {useGate} from '#/lib/statsig/statsig' import {emitSoftReset} from '#/state/events' import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed' import {FeedParams} from '#/state/queries/post-feed' import {usePreferencesQuery} from '#/state/queries/preferences' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {useSession} from '#/state/session' -import { - useMinimalShellMode, - useSetDrawerSwipeDisabled, - useSetMinimalShellMode, -} from '#/state/shell' +import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' import {useRequestNotificationsPermission} from 'lib/notifications/notifications' @@ -87,7 +81,6 @@ function HomeScreenReady({ const selectedIndex = Math.max(0, maybeFoundIndex) const selectedFeed = allFeeds[selectedIndex] const requestNotificationsPermission = useRequestNotificationsPermission() - const gate = useGate() useSetTitle(pinnedFeedInfos[selectedIndex]?.displayName) useOTAUpdates() @@ -134,29 +127,6 @@ function HomeScreenReady({ }), ) - const {footerMode} = useMinimalShellMode() - const {isMobile} = useWebMediaQueries() - useFocusEffect( - React.useCallback(() => { - if (gate('fixed_bottom_bar')) { - // Unnecessary because it's always there. - return - } - const listener = AppState.addEventListener('change', nextAppState => { - if (nextAppState === 'active') { - if (isMobile && footerMode.value === 1) { - // Reveal the bottom bar so you don't miss notifications or messages. - // TODO: Experiment with only doing it when unread > 0. - setMinimalShellMode(false) - } - } - }) - return () => { - listener.remove() - } - }, [setMinimalShellMode, footerMode, isMobile, gate]), - ) - const onPageSelected = React.useCallback( (index: number) => { setMinimalShellMode(false) From 292117804f986686c35c9fd3da1a8a4f22af49fc Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 7 Sep 2024 17:08:19 +0200 Subject: [PATCH 14/14] Set show_follow_suggestions_in_profile to true (#5205) --- src/components/FeedInterstitials.tsx | 8 +- src/lib/statsig/gates.ts | 1 - .../Profile/Header/ProfileHeaderStandard.tsx | 50 +---- .../profile/ProfileHeaderSuggestedFollows.tsx | 190 ------------------ 4 files changed, 3 insertions(+), 246 deletions(-) delete mode 100644 src/view/com/profile/ProfileHeaderSuggestedFollows.tsx diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index 65e981f77a..5031f584e5 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -8,7 +8,6 @@ import {useNavigation} from '@react-navigation/native' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {NavigationProp} from '#/lib/routes/types' -import {useGate} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -177,14 +176,9 @@ function useExperimentalSuggestedUsersQuery() { } export function SuggestedFollows({feed}: {feed: FeedDescriptor}) { - const gate = useGate() const [feedType, feedUri] = feed.split('|') if (feedType === 'author') { - if (gate('show_follow_suggestions_in_profile')) { - return - } else { - return null - } + return } else { return } diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 0c44b419e1..61a48e441a 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -3,7 +3,6 @@ export type Gate = | 'debug_show_feedcontext' | 'onboarding_minimum_interests' | 'suggested_feeds_interstitial' - | 'show_follow_suggestions_in_profile' | 'video_debug' // not recommended | 'video_upload' // upload videos | 'video_view_on_posts' // see posted videos diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 2036023c30..cf5fcb97e3 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -6,11 +6,9 @@ import { ModerationOpts, RichText as RichTextAPI, } from '@atproto/api' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' import {Shadow} from '#/state/cache/types' @@ -23,10 +21,9 @@ import {useRequireAuth, useSession} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' import {sanitizeDisplayName} from 'lib/strings/display-names' import {useProfileShadow} from 'state/cache/profile-shadow' -import {ProfileHeaderSuggestedFollows} from '#/view/com/profile/ProfileHeaderSuggestedFollows' import {ProfileMenu} from '#/view/com/profile/ProfileMenu' import * as Toast from '#/view/com/util/Toast' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {MessageProfileButton} from '#/components/dms/MessageProfileButton' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' @@ -59,8 +56,6 @@ let ProfileHeaderStandard = ({ }: Props): React.ReactNode => { const profile: Shadow = useProfileShadow(profileUnshadowed) - const t = useTheme() - const gate = useGate() const {currentAccount, hasSession} = useSession() const {_} = useLingui() const {openModal} = useModalControls() @@ -69,7 +64,6 @@ let ProfileHeaderStandard = ({ () => moderateProfile(profile, moderationOpts), [profile, moderationOpts], ) - const [showSuggestedFollows, setShowSuggestedFollows] = React.useState(false) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue( profile, 'ProfileHeader', @@ -202,34 +196,7 @@ let ProfileHeaderStandard = ({ ) ) : !profile.viewer?.blockedBy ? ( <> - {hasSession && ( - <> - - {!gate('show_follow_suggestions_in_profile') && ( - - )} - - )} + {hasSession && } - - - - - {isLoading ? ( - <> - - - - - - - ) : data ? ( - data.suggestions - .filter(s => (s.associated?.labeler ? false : true)) - .map(profile => ( - { - logEvent('profile:header:suggestedFollowsCard:press', {}) - }} - style={[a.flex_1]}> - {({hovered, pressed}) => ( - - - - - - - - - - - )} - - )) - ) : ( - - )} - - - - - ) -}