diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/App.native.tsx | 46 | ||||
-rw-r--r-- | src/Navigation.tsx | 2 | ||||
-rw-r--r-- | src/Splash.tsx | 154 | ||||
-rw-r--r-- | src/lib/assets.native.ts | 5 | ||||
-rw-r--r-- | src/lib/assets.ts | 8 | ||||
-rw-r--r-- | src/view/com/auth/SplashScreen.tsx | 16 | ||||
-rw-r--r-- | src/view/com/auth/SplashScreen.web.tsx | 20 | ||||
-rw-r--r-- | src/view/com/pager/FeedsTabBarMobile.tsx | 13 | ||||
-rw-r--r-- | src/view/icons/Logo.tsx | 48 | ||||
-rw-r--r-- | src/view/icons/Logotype.tsx | 28 | ||||
-rw-r--r-- | src/view/icons/index.tsx (renamed from src/view/icons.ts) | 0 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 18 |
12 files changed, 292 insertions, 66 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index d11d05e70..6402b4a89 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -6,6 +6,10 @@ import {RootSiblingParent} from 'react-native-root-siblings' import * as SplashScreen from 'expo-splash-screen' import {GestureHandlerRootView} from 'react-native-gesture-handler' import {QueryClientProvider} from '@tanstack/react-query' +import { + SafeAreaProvider, + initialWindowMetrics, +} from 'react-native-safe-area-context' import 'view/icons' @@ -34,6 +38,7 @@ import { } from 'state/session' import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import * as persisted from '#/state/persisted' +import {Splash} from '#/Splash' SplashScreen.preventAutoHideAsync() @@ -53,27 +58,28 @@ function InnerApp() { resumeSession(account) }, [resumeSession]) - // wait for session to resume - if (isInitialLoad) return null - return ( - <React.Fragment - // Resets the entire tree below when it changes: - key={currentAccount?.did}> - <LoggedOutViewProvider> - <UnreadNotifsProvider> - <ThemeProvider theme={colorMode}> - {/* All components should be within this provider */} - <RootSiblingParent> - <GestureHandlerRootView style={s.h100pct}> - <TestCtrls /> - <Shell /> - </GestureHandlerRootView> - </RootSiblingParent> - </ThemeProvider> - </UnreadNotifsProvider> - </LoggedOutViewProvider> - </React.Fragment> + <SafeAreaProvider initialMetrics={initialWindowMetrics}> + <Splash isReady={!isInitialLoad}> + <React.Fragment + // Resets the entire tree below when it changes: + key={currentAccount?.did}> + <LoggedOutViewProvider> + <UnreadNotifsProvider> + <ThemeProvider theme={colorMode}> + {/* All components should be within this provider */} + <RootSiblingParent> + <GestureHandlerRootView style={s.h100pct}> + <TestCtrls /> + <Shell /> + </GestureHandlerRootView> + </RootSiblingParent> + </ThemeProvider> + </UnreadNotifsProvider> + </LoggedOutViewProvider> + </React.Fragment> + </Splash> + </SafeAreaProvider> ) } diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 24fbb0d81..252699e53 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import {StyleSheet} from 'react-native' -import * as SplashScreen from 'expo-splash-screen' import { NavigationContainer, createNavigationContainerRef, @@ -493,7 +492,6 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) { linking={LINKING} theme={theme} onReady={() => { - SplashScreen.hideAsync() logModuleInitTime() onReady() }}> diff --git a/src/Splash.tsx b/src/Splash.tsx new file mode 100644 index 000000000..92ed366f9 --- /dev/null +++ b/src/Splash.tsx @@ -0,0 +1,154 @@ +import React, {useCallback, useEffect} from 'react' +import {View, StyleSheet} from 'react-native' +import * as SplashScreen from 'expo-splash-screen' +import LinearGradient from 'react-native-linear-gradient' +import Animated, { + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withTiming, + Easing, +} from 'react-native-reanimated' +import MaskedView from '@react-native-masked-view/masked-view' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import Svg, {Path, SvgProps} from 'react-native-svg' + +export const Logo = React.forwardRef(function LogoImpl(props: SvgProps, ref) { + const width = 1000 + const height = width * (67 / 64) + return ( + <Svg + fill="none" + // @ts-ignore it's fiiiiine + ref={ref} + viewBox="0 0 64 66" + style={{width, height}}> + <Path + fill="#fff" + d="M13.873 3.77C21.21 9.243 29.103 20.342 32 26.3v15.732c0-.335-.13.043-.41.858-1.512 4.414-7.418 21.642-20.923 7.87-7.111-7.252-3.819-14.503 9.125-16.692-7.405 1.252-15.73-.817-18.014-8.93C1.12 22.804 0 8.431 0 6.488 0-3.237 8.579-.18 13.873 3.77ZM50.127 3.77C42.79 9.243 34.897 20.342 32 26.3v15.732c0-.335.13.043.41.858 1.512 4.414 7.418 21.642 20.923 7.87 7.111-7.252 3.819-14.503-9.125-16.692 7.405 1.252 15.73-.817 18.014-8.93C62.88 22.804 64 8.431 64 6.488 64-3.237 55.422-.18 50.127 3.77Z" + /> + </Svg> + ) +}) + +type Props = { + isReady: boolean +} + +SplashScreen.preventAutoHideAsync().catch(() => {}) + +const AnimatedLogo = Animated.createAnimatedComponent(Logo) + +export function Splash(props: React.PropsWithChildren<Props>) { + const insets = useSafeAreaInsets() + const intro = useSharedValue(0) + const outroLogo = useSharedValue(0) + const outroApp = useSharedValue(0) + const [isAnimationComplete, setIsAnimationComplete] = React.useState(false) + + const logoAnimations = useAnimatedStyle(() => { + return { + transform: [ + { + scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'), + }, + { + scale: interpolate( + outroLogo.value, + [0, 0.06, 0.08, 1], + [1, 0.8, 0.8, 800], + 'clamp', + ), + }, + ], + opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'), + } + }) + + const appAnimation = useAnimatedStyle(() => { + return { + transform: [ + { + scale: interpolate( + outroApp.value, + [0, 0.7, 1], + [1.1, 1.1, 1], + 'clamp', + ), + }, + ], + opacity: interpolate(outroApp.value, [0, 0.7, 1], [0, 0, 1], 'clamp'), + } + }) + + const onFinish = useCallback(() => setIsAnimationComplete(true), []) + + useEffect(() => { + if (props.isReady) { + // hide on mount + SplashScreen.hideAsync().catch(() => {}) + + intro.value = withTiming( + 1, + {duration: 200, easing: Easing.out(Easing.cubic)}, + async () => { + outroLogo.value = withTiming( + 1, + {duration: 1200, easing: Easing.in(Easing.cubic)}, + () => { + runOnJS(onFinish)() + }, + ) + outroApp.value = withTiming( + 1, + {duration: 1200, easing: Easing.inOut(Easing.cubic)}, + () => { + runOnJS(onFinish)() + }, + ) + }, + ) + } + }, [onFinish, intro, outroLogo, outroApp, props.isReady]) + + return ( + <View style={{flex: 1}}> + {!isAnimationComplete && ( + <LinearGradient + colors={['#0A7AFF', '#59B9FF']} + style={[StyleSheet.absoluteFillObject]} + /> + )} + + <MaskedView + style={[StyleSheet.absoluteFillObject]} + maskElement={ + <Animated.View + style={[ + StyleSheet.absoluteFillObject, + { + // Transparent background because mask is based off alpha channel. + backgroundColor: 'transparent', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + transform: [{translateY: -(insets.top / 2)}, {scale: 0.1}], // scale from 1000px to 100px + }, + ]}> + <AnimatedLogo style={[logoAnimations]} /> + </Animated.View> + }> + {!isAnimationComplete && ( + <View + style={[StyleSheet.absoluteFillObject, {backgroundColor: 'white'}]} + /> + )} + + <Animated.View style={[{flex: 1}, appAnimation]}> + {props.children} + </Animated.View> + </MaskedView> + </View> + ) +} diff --git a/src/lib/assets.native.ts b/src/lib/assets.native.ts index d7ef9a05e..754bc9d2b 100644 --- a/src/lib/assets.native.ts +++ b/src/lib/assets.native.ts @@ -1,5 +1,4 @@ import {ImageRequireSource} from 'react-native' -export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.jpg') -export const TABS_EXPLAINER: ImageRequireSource = require('../../assets/tabs-explainer.jpg') -export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/cloud-splash.png') +export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.png') +export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/splash.png') diff --git a/src/lib/assets.ts b/src/lib/assets.ts index 216478762..8859607d5 100644 --- a/src/lib/assets.ts +++ b/src/lib/assets.ts @@ -1,10 +1,6 @@ import {ImageRequireSource} from 'react-native' // @ts-ignore we need to pretend -prf -export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.jpg'} +export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.png'} // @ts-ignore we need to pretend -prf -export const TABS_EXPLAINER: ImageRequireSource = { - uri: '/img/tabs-explainer.jpg', -} -// @ts-ignore we need to pretend -prf -export const CLOUD_SPLASH: ImageRequireSource = {uri: '/img/cloud-splash.png'} +export const CLOUD_SPLASH: ImageRequireSource = {uri: '/img/splash.png'} diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx index d88627f65..bb2d657ea 100644 --- a/src/view/com/auth/SplashScreen.tsx +++ b/src/view/com/auth/SplashScreen.tsx @@ -7,6 +7,8 @@ import {usePalette} from 'lib/hooks/usePalette' import {CenteredView} from '../util/Views' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' export const SplashScreen = ({ onPressSignin, @@ -22,11 +24,14 @@ export const SplashScreen = ({ <CenteredView style={[styles.container, pal.view]}> <ErrorBoundary> <View style={styles.hero}> - <Text style={[styles.title, pal.link]}> - <Trans>Bluesky</Trans> - </Text> - <Text style={[styles.subtitle, pal.textLight]}> - <Trans>See what's next</Trans> + <Logo width={92} fill="sky" /> + + <View style={{paddingTop: 40, paddingBottom: 6}}> + <Logotype width={161} /> + </View> + + <Text type="lg-medium" style={[pal.textLight]}> + <Trans>What's next?</Trans> </Text> </View> <View testID="signinOrCreateAccount" style={styles.btns}> @@ -65,6 +70,7 @@ const styles = StyleSheet.create({ hero: { flex: 2, justifyContent: 'center', + alignItems: 'center', }, btns: { paddingBottom: 40, diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index 08cf701da..4e942f66e 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -10,6 +10,8 @@ import {CenteredView} from '../util/Views' import {isWeb} from 'platform/detection' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {Trans} from '@lingui/macro' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' export const SplashScreen = ({ onDismiss, @@ -55,14 +57,15 @@ export const SplashScreen = ({ styles.containerInner, isMobileWeb && styles.containerInnerMobile, pal.border, + {alignItems: 'center'}, ]}> <ErrorBoundary> - <Text style={isMobileWeb ? styles.titleMobile : styles.title}> - Bluesky - </Text> - <Text style={isMobileWeb ? styles.subtitleMobile : styles.subtitle}> - See what's next - </Text> + <Logo width={92} fill="sky" /> + + <View style={{paddingTop: 40, paddingBottom: 20}}> + <Logotype width={161} /> + </View> + <View testID="signinOrCreateAccount" style={styles.btns}> <TouchableOpacity testID="createAccountButton" @@ -117,8 +120,6 @@ function Footer({styles}: {styles: ReturnType<typeof useStyles>}) { ) } const useStyles = () => { - const {isTabletOrMobile} = useWebMediaQueries() - const isMobileWeb = isWeb && isTabletOrMobile return StyleSheet.create({ container: { height: '100%', @@ -161,8 +162,7 @@ const useStyles = () => { paddingBottom: 30, }, btns: { - flexDirection: isMobileWeb ? 'column' : 'row', - gap: 20, + gap: 10, justifyContent: 'center', paddingBottom: 40, }, diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx index 882b6cfc5..024f9bfab 100644 --- a/src/view/com/pager/FeedsTabBarMobile.tsx +++ b/src/view/com/pager/FeedsTabBarMobile.tsx @@ -3,12 +3,9 @@ import {StyleSheet, TouchableOpacity, View} from 'react-native' import {TabBar} from 'view/com/pager/TabBar' import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {usePalette} from 'lib/hooks/usePalette' -import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' import {Link} from '../util/Link' -import {Text} from '../util/text/Text' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' -import {s} from 'lib/styles' import {HITSLOP_10} from 'lib/constants' import Animated from 'react-native-reanimated' import {msg} from '@lingui/macro' @@ -21,17 +18,17 @@ import {usePinnedFeedsInfos} from '#/state/queries/feed' import {isWeb} from 'platform/detection' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' +import {Logo} from '#/view/icons/Logo' export function FeedsTabBar( props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, ) { const pal = usePalette('default') - const {isSandbox, hasSession} = useSession() + const {hasSession} = useSession() const {_} = useLingui() const setDrawerOpen = useSetDrawerOpen() const navigation = useNavigation<NavigationProp>() const {feeds, hasPinnedCustom} = usePinnedFeedsInfos() - const brandBlue = useColorSchemeStyle(s.brandBlue, s.blue3) const {headerHeight} = useShellLayout() const {headerMinimalShellTransform} = useMinimalShellMode() const pinnedDisplayNames = hasSession ? feeds.map(f => f.displayName) : [] @@ -86,9 +83,9 @@ export function FeedsTabBar( /> </TouchableOpacity> </View> - <Text style={[brandBlue, s.bold, styles.title]}> - {isSandbox ? 'SANDBOX' : 'Bluesky'} - </Text> + <View> + <Logo width={30} /> + </View> <View style={[pal.view, {width: 18}]}> {hasSession && ( <Link diff --git a/src/view/icons/Logo.tsx b/src/view/icons/Logo.tsx new file mode 100644 index 000000000..15ab5a11c --- /dev/null +++ b/src/view/icons/Logo.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import Svg, { + Path, + Defs, + LinearGradient, + Stop, + SvgProps, + PathProps, +} from 'react-native-svg' + +import {colors} from '#/lib/styles' + +const ratio = 57 / 64 + +type Props = { + fill?: PathProps['fill'] +} & SvgProps + +export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) { + const {fill, ...rest} = props + const gradient = fill === 'sky' + const _fill = gradient ? 'url(#sky)' : fill || colors.blue3 + // @ts-ignore it's fiiiiine + const size = parseInt(rest.width || 32) + return ( + <Svg + fill="none" + // @ts-ignore it's fiiiiine + ref={ref} + viewBox="0 0 64 57" + {...rest} + style={{width: size, height: size * ratio}}> + {gradient && ( + <Defs> + <LinearGradient id="sky" x1="0" y1="0" x2="0" y2="1"> + <Stop offset="0" stopColor="#0A7AFF" stopOpacity="1" /> + <Stop offset="1" stopColor="#59B9FF" stopOpacity="1" /> + </LinearGradient> + </Defs> + )} + + <Path + fill={_fill} + d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z" + /> + </Svg> + ) +}) diff --git a/src/view/icons/Logotype.tsx b/src/view/icons/Logotype.tsx new file mode 100644 index 000000000..847607a3e --- /dev/null +++ b/src/view/icons/Logotype.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import Svg, {Path, SvgProps, PathProps} from 'react-native-svg' + +import {colors} from '#/lib/styles' + +const ratio = 17 / 64 + +export function Logotype({ + fill, + ...rest +}: {fill?: PathProps['fill']} & SvgProps) { + // @ts-ignore it's fiiiiine + const size = parseInt(rest.width || 32) + + return ( + <Svg + fill="none" + viewBox="0 0 64 17" + {...rest} + width={size} + height={Number(size) * ratio}> + <Path + fill={fill || colors.black} + d="M8.478 6.252c1.503.538 2.3 1.78 2.3 3.172 0 2.356-1.576 3.785-4.6 3.785H0V0h5.974c2.875 0 4.267 1.466 4.267 3.413 0 1.3-.594 2.245-1.763 2.839Zm-2.69-4.193H2.504v3.45h3.284c1.28 0 1.967-.667 1.967-1.78 0-1.02-.705-1.67-1.967-1.67Zm-3.284 9.072h3.544c1.41 0 2.17-.65 2.17-1.818 0-1.224-.723-1.837-2.17-1.837H2.504v3.655ZM14.251 13.209h-2.337V0h2.337v13.209ZM22.001 8.998V3.636h2.338v9.573h-2.263v-1.392c-.724 1.076-1.726 1.614-3.006 1.614-2.022 0-3.34-1.224-3.34-3.45V3.636h2.338v5.955c0 1.206.594 1.818 1.8 1.818 1.132 0 2.133-.835 2.133-2.411ZM34.979 8.59v.556h-7.161c.167 1.651 1.076 2.467 2.486 2.467 1.076 0 1.8-.463 2.189-1.372h2.244c-.5 1.947-2.17 3.19-4.452 3.19-1.428 0-2.579-.463-3.45-1.372-.872-.91-1.318-2.115-1.318-3.637 0-1.502.427-2.708 1.299-3.636.872-.909 2.004-1.372 3.432-1.372 1.447 0 2.597.482 3.45 1.428.854.946 1.28 2.208 1.28 3.747Zm-4.75-3.358c-1.28 0-2.17.742-2.393 2.281h4.805c-.204-1.391-1.057-2.281-2.411-2.281ZM40.16 13.469c-2.783 0-4.249-1.095-4.379-3.303h2.282c.13 1.188.724 1.633 2.134 1.633 1.261 0 1.892-.39 1.892-1.15 0-.687-.445-1.02-1.874-1.262l-1.094-.185c-2.097-.353-3.136-1.318-3.136-2.894 0-1.8 1.429-2.894 3.97-2.894 2.728 0 4.138 1.075 4.23 3.246h-2.207c-.056-1.169-.742-1.577-2.023-1.577-1.113 0-1.67.371-1.67 1.113 0 .668.483.965 1.596 1.169l1.206.186c2.32.426 3.32 1.28 3.32 2.912 0 1.93-1.557 3.006-4.247 3.006ZM54.667 13.209h-2.671l-2.783-4.453-1.447 1.447v3.006h-2.3V0h2.3v7.606l3.896-3.97h2.783l-3.618 3.618 3.84 5.955ZM60.772 6.048l.78-2.412H64l-3.692 10.352c-.39 1.057-.872 1.818-1.484 2.245-.612.426-1.484.63-2.634.63-.39 0-.724-.018-1.02-.055V14.97h.89c1.057 0 1.577-.65 1.577-1.54 0-.445-.149-1.094-.446-1.929l-2.746-7.866h2.487l.779 2.393c.575 1.8 1.076 3.58 1.521 5.343.408-1.521.928-3.302 1.54-5.324Z" + /> + </Svg> + ) +} diff --git a/src/view/icons.ts b/src/view/icons/index.tsx index 089d3f0a8..089d3f0a8 100644 --- a/src/view/icons.ts +++ b/src/view/icons/index.tsx diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 18042c7ed..51c03ae3d 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -20,10 +20,6 @@ import {usePalette} from 'lib/hooks/usePalette' import {RoutesContainer, TabsNavigator} from '../../Navigation' import {isStateAtTabRoot} from 'lib/routes/helpers' import { - SafeAreaProvider, - initialWindowMetrics, -} from 'react-native-safe-area-context' -import { useIsDrawerOpen, useSetDrawerOpen, useIsDrawerSwipeDisabled, @@ -107,14 +103,12 @@ export const Shell: React.FC = function ShellImpl() { const pal = usePalette('default') const theme = useTheme() return ( - <SafeAreaProvider initialMetrics={initialWindowMetrics} style={pal.view}> - <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> - <StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} /> - <RoutesContainer> - <ShellInner /> - </RoutesContainer> - </View> - </SafeAreaProvider> + <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}> + <StatusBar style={theme.colorScheme === 'dark' ? 'light' : 'dark'} /> + <RoutesContainer> + <ShellInner /> + </RoutesContainer> + </View> ) } |