* Add new assets

* Add splashiness

* Add butter icon, spread it

* Cream together eggs, sugar, and vanilla

* Hi, I'd like to place and order. Yeah, none pizza with left beef, plz.

* test

* Refine animation

* tweak

* tweak

* tweak

* Tweak

* Simplify

* Cleanup

* Fix android logo

---------

Co-authored-by: Ansh Nanda <anshnanda10@gmail.com>
zio/stable
Eric Bailey 2023-12-14 14:48:40 -06:00 committed by GitHub
parent 075ffdf583
commit 7897dd24a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 304 additions and 69 deletions

View File

@ -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',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

BIN
assets/splash.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

View File

@ -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",

View File

@ -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>
)
}

View File

@ -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()
}}>

154
src/Splash.tsx 100644
View File

@ -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>
)
}

View File

@ -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')

View File

@ -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'}

View File

@ -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,

View File

@ -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,
},

View File

@ -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

View File

@ -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>
)
})

View File

@ -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>
)
}

View File

@ -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 (
<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>
)
}

View File

@ -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"