mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Milly tweaks (#5365)

Co-authored-by: Hailey <me@haileyok.com>

authored by

Eric Bailey
Hailey
and committed by
GitHub
b69fd234 8daf6b78

+304 -69
+3 -1
src/components/Prompt.tsx
··· 59 59 export function TitleText({children}: React.PropsWithChildren<{}>) { 60 60 const {titleId} = React.useContext(Context) 61 61 return ( 62 - <Text nativeID={titleId} style={[a.text_2xl, a.font_bold, a.pb_sm]}> 62 + <Text 63 + nativeID={titleId} 64 + style={[a.text_2xl, a.font_bold, a.pb_sm, a.leading_snug]}> 63 65 {children} 64 66 </Text> 65 67 )
+129
src/components/dialogs/nuxs/TenMillion/Trigger.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + import Svg, {Circle, Path} from 'react-native-svg' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import {Nux, useUpsertNuxMutation} from '#/state/queries/nuxs' 8 + import {atoms as a, ViewStyleProp} from '#/alf' 9 + import {Button, ButtonProps} from '#/components/Button' 10 + import * as Dialog from '#/components/Dialog' 11 + import {InlineLinkText} from '#/components/Link' 12 + import * as Prompt from '#/components/Prompt' 13 + import {TenMillion} from './' 14 + 15 + export function Trigger({children}: {children: ButtonProps['children']}) { 16 + const {_} = useLingui() 17 + const {mutate: upsertNux} = useUpsertNuxMutation() 18 + const [show, setShow] = React.useState(false) 19 + const [fallback, setFallback] = React.useState(false) 20 + const control = Prompt.usePromptControl() 21 + 22 + const handleOnPress = () => { 23 + if (!fallback) { 24 + setShow(true) 25 + upsertNux({ 26 + id: Nux.TenMillionDialog, 27 + completed: true, 28 + data: undefined, 29 + }) 30 + } else { 31 + control.open() 32 + } 33 + } 34 + 35 + const onHandleFallback = () => { 36 + setFallback(true) 37 + control.open() 38 + } 39 + 40 + return ( 41 + <> 42 + <Button 43 + label={_(msg`Bluesky is celebrating 10 million users!`)} 44 + onPress={handleOnPress}> 45 + {children} 46 + </Button> 47 + 48 + {show && !fallback && ( 49 + <TenMillion 50 + showTimeout={0} 51 + onClose={() => setShow(false)} 52 + onFallback={onHandleFallback} 53 + /> 54 + )} 55 + 56 + <Prompt.Outer control={control}> 57 + <View style={{maxWidth: 300}}> 58 + <Prompt.TitleText> 59 + <Trans>Bluesky is celebrating 10 million users!</Trans> 60 + </Prompt.TitleText> 61 + </View> 62 + <Prompt.DescriptionText> 63 + <Trans> 64 + Together, we're rebuilding the social internet. We're glad you're 65 + here. 66 + </Trans> 67 + </Prompt.DescriptionText> 68 + <Prompt.DescriptionText> 69 + <Trans> 70 + To learn more,{' '} 71 + <InlineLinkText 72 + label={_(msg`View our post`)} 73 + to="/profile/bsky.app/post/3l47prg3wgy23" 74 + onPress={() => { 75 + control.close() 76 + }} 77 + style={[a.text_md, a.leading_snug]}> 78 + <Trans>check out our post.</Trans> 79 + </InlineLinkText> 80 + </Trans> 81 + </Prompt.DescriptionText> 82 + <Dialog.Close /> 83 + </Prompt.Outer> 84 + </> 85 + ) 86 + } 87 + 88 + export function Icon({width, style}: {width: number} & ViewStyleProp) { 89 + return ( 90 + <Svg width={width} height={width} viewBox="0 0 36 36" style={style}> 91 + <Path 92 + fill="#dd2e44" 93 + d="M11.626 7.488a1.4 1.4 0 0 0-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937c.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269c1.562-1.562-.971-6.627-5.656-11.313c-4.687-4.686-9.752-7.218-11.315-5.656" 94 + /> 95 + <Path 96 + fill="#ea596e" 97 + d="M13 12L.416 32.506l-.282.635l.011.011c-.208.403.14 1.223.853 1.937c.232.232.473.408.709.557L17 17z" 98 + /> 99 + <Path 100 + fill="#a0041e" 101 + d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124c-1.473 1.474-6.453-1.118-11.126-5.788c-4.671-4.672-7.263-9.654-5.79-11.127c1.474-1.473 6.454 1.119 11.127 5.791" 102 + /> 103 + <Path 104 + fill="#aa8dd8" 105 + d="M18.59 13.609a1 1 0 0 1-.734.215c-.868-.094-1.598-.396-2.109-.873c-.541-.505-.808-1.183-.735-1.862c.128-1.192 1.324-2.286 3.363-2.066c.793.085 1.147-.17 1.159-.292c.014-.121-.277-.446-1.07-.532c-.868-.094-1.598-.396-2.11-.873c-.541-.505-.809-1.183-.735-1.862c.13-1.192 1.325-2.286 3.362-2.065c.578.062.883-.057 1.012-.134c.103-.063.144-.123.148-.158c.012-.121-.275-.446-1.07-.532a1 1 0 0 1-.886-1.102a.997.997 0 0 1 1.101-.886c2.037.219 2.973 1.542 2.844 2.735c-.13 1.194-1.325 2.286-3.364 2.067c-.578-.063-.88.057-1.01.134c-.103.062-.145.123-.149.157c-.013.122.276.446 1.071.532c2.037.22 2.973 1.542 2.844 2.735s-1.324 2.286-3.362 2.065c-.578-.062-.882.058-1.012.134c-.104.064-.144.124-.148.158c-.013.121.276.446 1.07.532a1 1 0 0 1 .52 1.773" 106 + /> 107 + <Path 108 + fill="#77b255" 109 + d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478c.324 1.154-.378 2.615-2.35 3.17c-.77.216-1.001.584-.97.701c.034.118.425.312 1.193.095c1.972-.555 3.333.325 3.657 1.479c.326 1.155-.378 2.614-2.351 3.17c-.769.216-1.001.585-.967.702s.423.311 1.192.095a1 1 0 1 1 .54 1.925c-1.971.555-3.333-.323-3.659-1.479c-.324-1.154.379-2.613 2.353-3.169c.77-.217 1.001-.584.967-.702c-.032-.117-.422-.312-1.19-.096c-1.974.556-3.334-.322-3.659-1.479c-.325-1.154.378-2.613 2.351-3.17c.768-.215.999-.585.967-.701c-.034-.118-.423-.312-1.192-.096a1 1 0 1 1-.54-1.923" 110 + /> 111 + <Path 112 + fill="#aa8dd8" 113 + d="M23.001 20.16a1.001 1.001 0 0 1-.626-1.781c.218-.175 5.418-4.259 12.767-3.208a1 1 0 1 1-.283 1.979c-6.493-.922-11.187 2.754-11.233 2.791a1 1 0 0 1-.625.219" 114 + /> 115 + <Path 116 + fill="#77b255" 117 + d="M5.754 16a1 1 0 0 1-.958-1.287c1.133-3.773 2.16-9.794.898-11.364c-.141-.178-.354-.353-.842-.316c-.938.072-.849 2.051-.848 2.071a1 1 0 1 1-1.994.149c-.103-1.379.326-4.035 2.692-4.214c1.056-.08 1.933.287 2.552 1.057c2.371 2.951-.036 11.506-.542 13.192a1 1 0 0 1-.958.712" 118 + /> 119 + <Circle cx="25.5" cy="9.5" r="1.5" fill="#5c913b" /> 120 + <Circle cx="2" cy="18" r="2" fill="#9266cc" /> 121 + <Circle cx="32.5" cy="19.5" r="1.5" fill="#5c913b" /> 122 + <Circle cx="23.5" cy="31.5" r="1.5" fill="#5c913b" /> 123 + <Circle cx="28" cy="4" r="2" fill="#ffcc4d" /> 124 + <Circle cx="32.5" cy="8.5" r="1.5" fill="#ffcc4d" /> 125 + <Circle cx="29.5" cy="12.5" r="1.5" fill="#ffcc4d" /> 126 + <Circle cx="7.5" cy="23.5" r="1.5" fill="#ffcc4d" /> 127 + </Svg> 128 + ) 129 + }
+41 -9
src/components/dialogs/nuxs/TenMillion/index.tsx
··· 87 87 ) 88 88 } 89 89 90 - export function TenMillion() { 90 + export function TenMillion({ 91 + showTimeout, 92 + onClose, 93 + onFallback, 94 + }: { 95 + showTimeout?: number 96 + onClose?: () => void 97 + onFallback?: () => void 98 + }) { 91 99 const agent = useAgent() 92 100 const nuxDialogs = useNuxDialogContext() 93 101 const [userNumber, setUserNumber] = React.useState<number>(0) ··· 120 128 } else { 121 129 // should be rare 122 130 nuxDialogs.dismissActiveNux() 131 + onFallback?.() 123 132 } 133 + } else { 134 + nuxDialogs.dismissActiveNux() 135 + onFallback?.() 124 136 } 125 137 } 126 138 ··· 128 140 fetching.current = true 129 141 networkRetry(3, fetchUserNumber).catch(() => { 130 142 nuxDialogs.dismissActiveNux() 143 + onFallback?.() 131 144 }) 132 145 } 133 146 }, [ ··· 136 149 setUserNumber, 137 150 nuxDialogs.dismissActiveNux, 138 151 nuxDialogs, 152 + onFallback, 139 153 ]) 140 154 141 - return userNumber ? <TenMillionInner userNumber={userNumber} /> : null 155 + return userNumber ? ( 156 + <TenMillionInner 157 + userNumber={userNumber} 158 + showTimeout={showTimeout ?? 3e3} 159 + onClose={onClose} 160 + /> 161 + ) : null 142 162 } 143 163 144 - export function TenMillionInner({userNumber}: {userNumber: number}) { 164 + export function TenMillionInner({ 165 + userNumber, 166 + showTimeout, 167 + onClose: onCloseOuter, 168 + }: { 169 + userNumber: number 170 + showTimeout: number 171 + onClose?: () => void 172 + }) { 145 173 const t = useTheme() 146 174 const lightTheme = useTheme('light') 147 175 const {_, i18n} = useLingui() ··· 184 212 React.useEffect(() => { 185 213 const timeout = setTimeout(() => { 186 214 control.open() 187 - }, 3e3) 215 + }, showTimeout) 188 216 return () => { 189 217 clearTimeout(timeout) 190 218 } 191 - }, [control]) 219 + }, [control, showTimeout]) 192 220 const onClose = React.useCallback(() => { 193 221 nuxDialogs.dismissActiveNux() 194 - }, [nuxDialogs]) 222 + onCloseOuter?.() 223 + }, [nuxDialogs, onCloseOuter]) 195 224 196 225 /* 197 226 * Actions ··· 617 646 a.gap_md, 618 647 a.pt_xl, 619 648 ]}> 620 - <Text style={[a.text_md, a.italic, t.atoms.text_contrast_medium]}> 621 - <Trans>Brag a little!</Trans> 622 - </Text> 649 + {gtMobile && ( 650 + <Text 651 + style={[a.text_md, a.italic, t.atoms.text_contrast_medium]}> 652 + <Trans>Brag a little!</Trans> 653 + </Text> 654 + )} 623 655 624 656 <Button 625 657 disabled={isLoadingImage}
+5 -37
src/components/dialogs/nuxs/index.tsx
··· 19 19 dismissActiveNux: () => void 20 20 } 21 21 22 - /** 23 - * If we fail to complete a NUX here, it may show again on next reload, 24 - * or if prefs state updates. If `true`, this fallback ensures that the last 25 - * shown NUX won't show again, at least for this session. 26 - * 27 - * This is temporary, and only needed for the 10Milly dialog rn, since we 28 - * aren't snoozing that one in device storage. 29 - */ 30 - let __isSnoozedFallback = false 31 - 32 22 const queuedNuxs: { 33 23 id: Nux 34 - enabled(props: {gate: ReturnType<typeof useGate>}): boolean 35 - /** 36 - * TEMP only intended for use with the 10Milly dialog rn, since there are no 37 - * other NUX dialogs configured 38 - */ 39 - unsafe_disableSnooze: boolean 24 + enabled?: (props: {gate: ReturnType<typeof useGate>}) => boolean 40 25 }[] = [ 41 26 { 42 27 id: Nux.TenMillionDialog, 43 - enabled({gate}) { 44 - return gate('ten_million_dialog') 45 - }, 46 - unsafe_disableSnooze: true, 47 28 }, 48 29 ] 49 30 ··· 92 73 } 93 74 94 75 React.useEffect(() => { 95 - if (__isSnoozedFallback) return 96 76 if (snoozed) return 97 77 if (!nuxs) return 98 78 99 - for (const {id, enabled, unsafe_disableSnooze} of queuedNuxs) { 79 + for (const {id, enabled} of queuedNuxs) { 100 80 const nux = nuxs.find(nux => nux.id === id) 101 81 102 82 // check if completed first 103 83 if (nux && nux.completed) continue 104 84 105 85 // then check gate (track exposure) 106 - if (!enabled({gate})) continue 86 + if (enabled && !enabled({gate})) continue 107 87 108 88 // we have a winner 109 89 setActiveNux(id) 110 90 111 - /** 112 - * TEMP only intended for use with the 10Milly dialog rn, since there are no 113 - * other NUX dialogs configured 114 - */ 115 - if (!unsafe_disableSnooze) { 116 - // immediately snooze for a day 117 - snoozeNuxDialog() 118 - } 91 + // immediately snooze for a day 92 + snoozeNuxDialog() 119 93 120 94 // immediately update remote data (affects next reload) 121 95 upsertNux({ ··· 126 100 logger.error(`NUX dialogs: failed to upsert '${id}' NUX`, { 127 101 safeMessage: e.message, 128 102 }) 129 - /* 130 - * TEMP only intended for use with the 10Milly dialog rn 131 - */ 132 - if (unsafe_disableSnooze) { 133 - __isSnoozedFallback = true 134 - } 135 103 }) 136 104 137 105 break
-4
src/lib/hooks/useIntentHandler.ts
··· 97 97 if (part.includes('https://') || part.includes('http://')) { 98 98 return false 99 99 } 100 - console.log({ 101 - part, 102 - text: VALID_IMAGE_REGEX.test(part), 103 - }) 104 100 // We also should just filter out cases that don't have all the info we need 105 101 return VALID_IMAGE_REGEX.test(part) 106 102 })
+1 -3
src/lib/statsig/gates.ts
··· 1 1 export type Gate = 2 2 // Keep this alphabetic please. 3 - | 'debug_show_feedcontext' 4 - | 'suggested_feeds_interstitial' 5 - | 'ten_million_dialog' 3 + 'debug_show_feedcontext' | 'suggested_feeds_interstitial'
+63 -10
src/view/com/home/HomeHeaderLayout.web.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 - import Animated from 'react-native-reanimated' 3 + import Animated, { 4 + useAnimatedStyle, 5 + useReducedMotion, 6 + useSharedValue, 7 + withDelay, 8 + withRepeat, 9 + withSequence, 10 + withSpring, 11 + withTiming, 12 + } from 'react-native-reanimated' 4 13 import {msg} from '@lingui/macro' 5 14 import {useLingui} from '@lingui/react' 6 15 ··· 8 17 import {useShellLayout} from '#/state/shell/shell-layout' 9 18 import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform' 10 19 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 11 - import {Logo} from '#/view/icons/Logo' 20 + // import {Logo} from '#/view/icons/Logo' 12 21 import {atoms as a, useTheme} from '#/alf' 22 + import {Icon, Trigger} from '#/components/dialogs/nuxs/TenMillion/Trigger' 13 23 import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag' 14 24 import {Link} from '#/components/Link' 15 - import {useKawaiiMode} from '../../../state/preferences/kawaii' 16 25 import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' 17 26 18 27 export function HomeHeaderLayout(props: { ··· 40 49 const {hasSession} = useSession() 41 50 const {_} = useLingui() 42 51 43 - const kawaii = useKawaiiMode() 52 + // TEMPORARY - REMOVE AFTER MILLY 53 + // This will just cause the icon to shake a bit when the user first opens the app, drawing attention to the celebration 54 + // 🎉 55 + const rotate = useSharedValue(0) 56 + const reducedMotion = useReducedMotion() 57 + 58 + // Run this a single time on app mount. 59 + React.useEffect(() => { 60 + if (reducedMotion) return 61 + 62 + // Waits 1500ms, then rotates 10 degrees with a spring animation. Repeats once. 63 + rotate.value = withDelay( 64 + 1000, 65 + withRepeat( 66 + withSequence( 67 + withTiming(10, {duration: 100}), 68 + withSpring(0, { 69 + mass: 1, 70 + damping: 1, 71 + stiffness: 200, 72 + overshootClamping: false, 73 + }), 74 + ), 75 + 2, 76 + false, 77 + ), 78 + ) 79 + }, [rotate, reducedMotion]) 80 + 81 + const animatedStyle = useAnimatedStyle(() => ({ 82 + transform: [ 83 + { 84 + rotateZ: `${rotate.value}deg`, 85 + }, 86 + ], 87 + })) 44 88 45 89 return ( 46 90 <> ··· 57 101 t.atoms.bg, 58 102 t.atoms.border_contrast_low, 59 103 styles.bar, 60 - kawaii && {paddingTop: 22, paddingBottom: 16}, 61 104 ]}> 62 - <View 105 + <Animated.View 63 106 style={[ 64 107 a.absolute, 65 108 a.inset_0, 66 109 a.pt_lg, 67 110 a.m_auto, 68 - kawaii && {paddingTop: 4, paddingBottom: 0}, 69 111 { 70 - width: kawaii ? 84 : 28, 112 + width: 28, 71 113 }, 114 + animatedStyle, 72 115 ]}> 73 - <Logo width={kawaii ? 60 : 28} /> 74 - </View> 116 + <Trigger> 117 + {ctx => ( 118 + <Icon 119 + width={28} 120 + style={{ 121 + opacity: ctx.hovered || ctx.pressed ? 0.8 : 1, 122 + }} 123 + /> 124 + )} 125 + </Trigger> 126 + {/* <Logo width={28} /> */} 127 + </Animated.View> 75 128 76 129 <Link 77 130 to="/feeds"
+62 -5
src/view/com/home/HomeHeaderLayoutMobile.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, TouchableOpacity, View} from 'react-native' 3 - import Animated from 'react-native-reanimated' 3 + import Animated, { 4 + useAnimatedStyle, 5 + useReducedMotion, 6 + useSharedValue, 7 + withDelay, 8 + withRepeat, 9 + withSequence, 10 + withSpring, 11 + withTiming, 12 + } from 'react-native-reanimated' 4 13 import {msg} from '@lingui/macro' 5 14 import {useLingui} from '@lingui/react' 6 15 ··· 11 20 import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform' 12 21 import {usePalette} from 'lib/hooks/usePalette' 13 22 import {isWeb} from 'platform/detection' 14 - import {Logo} from '#/view/icons/Logo' 23 + // import {Logo} from '#/view/icons/Logo' 15 24 import {atoms} from '#/alf' 16 25 import {useTheme} from '#/alf' 17 26 import {atoms as a} from '#/alf' 27 + import {Icon, Trigger} from '#/components/dialogs/nuxs/TenMillion/Trigger' 18 28 import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette' 19 29 import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag' 20 30 import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' ··· 39 49 setDrawerOpen(true) 40 50 }, [setDrawerOpen]) 41 51 52 + // TEMPORARY - REMOVE AFTER MILLY 53 + // This will just cause the icon to shake a bit when the user first opens the app, drawing attention to the celebration 54 + // 🎉 55 + const rotate = useSharedValue(0) 56 + const reducedMotion = useReducedMotion() 57 + 58 + // Run this a single time on app mount. 59 + React.useEffect(() => { 60 + if (reducedMotion) return 61 + 62 + // Waits 1500ms, then rotates 10 degrees with a spring animation. Repeats once. 63 + rotate.value = withDelay( 64 + 1000, 65 + withRepeat( 66 + withSequence( 67 + withTiming(10, {duration: 100}), 68 + withSpring(0, { 69 + mass: 1, 70 + damping: 1, 71 + stiffness: 200, 72 + overshootClamping: false, 73 + }), 74 + ), 75 + 2, 76 + false, 77 + ), 78 + ) 79 + }, [rotate, reducedMotion]) 80 + 81 + const animatedStyle = useAnimatedStyle(() => ({ 82 + transform: [ 83 + { 84 + rotateZ: `${rotate.value}deg`, 85 + }, 86 + ], 87 + })) 88 + 42 89 return ( 43 90 <Animated.View 44 91 style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]} ··· 59 106 <Menu size="lg" fill={t.atoms.text_contrast_medium.color} /> 60 107 </TouchableOpacity> 61 108 </View> 62 - <View> 63 - <Logo width={30} /> 64 - </View> 109 + <Animated.View style={animatedStyle}> 110 + <Trigger> 111 + {ctx => ( 112 + <Icon 113 + width={28} 114 + style={{ 115 + opacity: ctx.pressed ? 0.8 : 1, 116 + }} 117 + /> 118 + )} 119 + </Trigger> 120 + {/* <Logo width={30} /> */} 121 + </Animated.View> 65 122 <View 66 123 style={[ 67 124 atoms.flex_row,