diff --git a/app.config.js b/app.config.js index 096756b084..d16b494d09 100644 --- a/app.config.js +++ b/app.config.js @@ -43,7 +43,7 @@ module.exports = function () { icon: './assets/icon.png', userInterfaceStyle: 'automatic', splash: { - image: './assets/cloud-splash.png', + image: './assets/splash.png', resizeMode: 'cover', backgroundColor: '#ffffff', }, @@ -73,9 +73,12 @@ module.exports = function () { }, android: { versionCode: ANDROID_VERSION_CODE, + icon: './assets/icon.png', adaptiveIcon: { - foregroundImage: './assets/adaptive-icon.png', - backgroundColor: '#ffffff', + foregroundImage: './assets/icon-android-foreground.png', + monochromeImage: './assets/icon-android-foreground.png', + backgroundImage: './assets/icon-android-background.png', + backgroundColor: '#1185FE', }, googleServicesFile: './google-services.json', package: 'xyz.blueskyweb.app', diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png deleted file mode 100644 index 1dda9a342c..0000000000 Binary files a/assets/adaptive-icon.png and /dev/null differ diff --git a/assets/cloud-splash.png b/assets/cloud-splash.png deleted file mode 100644 index 188625331d..0000000000 Binary files a/assets/cloud-splash.png and /dev/null differ diff --git a/assets/default-avatar.jpg b/assets/default-avatar.jpg deleted file mode 100644 index ab141651e7..0000000000 Binary files a/assets/default-avatar.jpg and /dev/null differ diff --git a/assets/default-avatar.png b/assets/default-avatar.png new file mode 100644 index 0000000000..d2bd03842f Binary files /dev/null and b/assets/default-avatar.png differ diff --git a/assets/favicon.png b/assets/favicon.png index 24ec61e0a5..ddf55f4c81 100644 Binary files a/assets/favicon.png and b/assets/favicon.png differ diff --git a/assets/icon-android-background.png b/assets/icon-android-background.png new file mode 100644 index 0000000000..2c9d153823 Binary files /dev/null and b/assets/icon-android-background.png differ diff --git a/assets/icon-android-foreground.png b/assets/icon-android-foreground.png new file mode 100644 index 0000000000..7a6d477d6f Binary files /dev/null and b/assets/icon-android-foreground.png differ diff --git a/assets/icon.png b/assets/icon.png index 1dda9a342c..7dc2c31a67 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/splash-with-logo.png b/assets/splash-with-logo.png new file mode 100644 index 0000000000..d012d724c2 Binary files /dev/null and b/assets/splash-with-logo.png differ diff --git a/assets/splash.png b/assets/splash.png new file mode 100644 index 0000000000..603f1f3791 Binary files /dev/null and b/assets/splash.png differ diff --git a/assets/tabs-explainer.jpg b/assets/tabs-explainer.jpg deleted file mode 100644 index 64f0a8fe5b..0000000000 Binary files a/assets/tabs-explainer.jpg and /dev/null differ diff --git a/package.json b/package.json index 52e4b7eff5..d1d12e2237 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@react-native-clipboard/clipboard": "^1.10.0", "@react-native-community/blur": "^4.3.0", "@react-native-community/datetimepicker": "7.2.0", + "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-menu/menu": "^0.8.0", "@react-native-picker/picker": "2.4.10", "@react-navigation/bottom-tabs": "^6.5.7", diff --git a/src/App.native.tsx b/src/App.native.tsx index d11d05e705..6402b4a898 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 ( - - - - - {/* All components should be within this provider */} - - - - - - - - - - + + + + + + + {/* All components should be within this provider */} + + + + + + + + + + + + ) } diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 24fbb0d818..252699e538 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 0000000000..92ed366f96 --- /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 ( + + + + ) +}) + +type Props = { + isReady: boolean +} + +SplashScreen.preventAutoHideAsync().catch(() => {}) + +const AnimatedLogo = Animated.createAnimatedComponent(Logo) + +export function Splash(props: React.PropsWithChildren) { + 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 ( + + {!isAnimationComplete && ( + + )} + + + + + }> + {!isAnimationComplete && ( + + )} + + + {props.children} + + + + ) +} diff --git a/src/lib/assets.native.ts b/src/lib/assets.native.ts index d7ef9a05eb..754bc9d2b0 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 216478762d..8859607d56 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 d88627f65a..bb2d657ea4 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 = ({ - - Bluesky - - - See what's next + + + + + + + + What's next? @@ -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 08cf701da6..4e942f66e3 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'}, ]}> - - Bluesky - - - See what's next - + + + + + + }) { ) } 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 882b6cfc54..024f9bfab3 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() 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( /> - - {isSandbox ? 'SANDBOX' : 'Bluesky'} - + + + {hasSession && ( + {gradient && ( + + + + + + + )} + + + + ) +}) diff --git a/src/view/icons/Logotype.tsx b/src/view/icons/Logotype.tsx new file mode 100644 index 0000000000..847607a3e4 --- /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 ( + + + + ) +} diff --git a/src/view/icons.ts b/src/view/icons/index.tsx similarity index 100% rename from src/view/icons.ts rename to src/view/icons/index.tsx diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 18042c7ed2..51c03ae3dd 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -19,10 +19,6 @@ import {useTheme} from 'lib/ThemeContext' 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, @@ -107,14 +103,12 @@ export const Shell: React.FC = function ShellImpl() { const pal = usePalette('default') const theme = useTheme() return ( - - - - - - - - + + + + + + ) } diff --git a/yarn.lock b/yarn.lock index f2415d6aea..ab569ca5b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4448,6 +4448,11 @@ resolved "https://registry.yarnpkg.com/@react-native-community/eslint-plugin/-/eslint-plugin-1.3.0.tgz#9e558170c106bbafaa1ef502bd8e6d4651012bf9" integrity sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg== +"@react-native-masked-view/masked-view@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@react-native-masked-view/masked-view/-/masked-view-0.3.1.tgz#5bd76f17004a6ccbcec03856893777ee91f23d29" + integrity sha512-uVm8U6nwFIlUd1iDIB5cS+lDadApKR+l8k4k84d9hn+GN4lzAIJhUZ9syYX7c022MxNgAlbxoFLt0pqKoyaAGg== + "@react-native-menu/menu@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@react-native-menu/menu/-/menu-0.8.0.tgz#dbf227c2081e5ffd3d2073ee68ecc84cf8639727"