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"