An ATproto social media client -- with an independent Appview.

Add kawaii mode (#3773)

authored by samuel.fm and committed by GitHub 81ae7e42 181e61be

Changed files
+150 -28
.github
assets
bskyweb
cmd
bskyweb
static
media
src
state
persisted
preferences
view
+4 -4
.github/workflows/golang-test-lint.yml
··· 20 20 uses: actions/setup-go@v3 21 21 with: 22 22 go-version: '1.21' 23 - - name: Dummy JS File 24 - run: touch bskyweb/static/js/blah.js 23 + - name: Dummy Static Files 24 + run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt 25 25 - name: Check 26 26 run: cd bskyweb/ && make check 27 27 - name: Build (binary) ··· 37 37 uses: actions/setup-go@v3 38 38 with: 39 39 go-version: '1.21' 40 - - name: Dummy JS File 41 - run: touch bskyweb/static/js/blah.js 40 + - name: Dummy Static Files 41 + run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt 42 42 - name: Lint 43 43 run: cd bskyweb/ && make lint
+1
Dockerfile.embedr
··· 40 40 41 41 # hack around issue with empty directory and go:embed 42 42 RUN touch bskyweb/static/js/empty.txt 43 + RUN touch bskyweb/static/media/empty.txt 43 44 44 45 # 45 46 # Generate the embedr Go binary.
assets/kawaii.png

This is a binary file and will not be displayed.

assets/kawaii_smol.png

This is a binary file and will not be displayed.

+2
bskyweb/.gitignore
··· 10 10 static/js/*.map 11 11 static/js/*.js.LICENSE.txt 12 12 static/js/empty.txt 13 + static/media/*.png 14 + static/media/empty.txt 13 15 templates/scripts.html 14 16 templates/*-embed.html 15 17 static/embed/*.html
+1 -1
bskyweb/cmd/bskyweb/server.go
··· 158 158 159 159 // Cache javascript and images files for 1 week, which works because 160 160 // they're always versioned (e.g. /static/js/main.64c14927.js) 161 - if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") { 161 + if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") || strings.HasPrefix(path, "/static/media/") { 162 162 maxAge = 7 * (60 * 60 * 24) // 1 week 163 163 } 164 164
bskyweb/static/media/.gitkeep

This is a binary file and will not be displayed.

+1 -1
package.json
··· 15 15 "web": "expo start --web", 16 16 "use-build-number": "./scripts/useBuildNumberEnv.sh", 17 17 "use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh", 18 - "build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/", 18 + "build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/ && cp -v ./web-build/static/media/*.png ./bskyweb/static/media/", 19 19 "build-all": "yarn intl:build && yarn use-build-number-with-bump eas build --platform all", 20 20 "build-ios": "yarn use-build-number-with-bump eas build -p ios", 21 21 "build-android": "yarn use-build-number-with-bump eas build -p android",
+4 -4
src/state/persisted/index.ts
··· 1 1 import EventEmitter from 'eventemitter3' 2 + 3 + import BroadcastChannel from '#/lib/broadcast' 2 4 import {logger} from '#/logger' 3 - import {defaults, Schema} from '#/state/persisted/schema' 4 5 import {migrate} from '#/state/persisted/legacy' 6 + import {defaults, Schema} from '#/state/persisted/schema' 5 7 import * as store from '#/state/persisted/store' 6 - import BroadcastChannel from '#/lib/broadcast' 7 - 8 - export type {Schema, PersistedAccount} from '#/state/persisted/schema' 8 + export type {PersistedAccount, Schema} from '#/state/persisted/schema' 9 9 export {defaults} from '#/state/persisted/schema' 10 10 11 11 const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')
+2
src/state/persisted/schema.ts
··· 80 80 pdsAddressHistory: z.array(z.string()).optional(), 81 81 disableHaptics: z.boolean().optional(), 82 82 disableAutoplay: z.boolean().optional(), 83 + kawaii: z.boolean().optional(), 83 84 }) 84 85 export type Schema = z.infer<typeof schema> 85 86 ··· 117 118 pdsAddressHistory: [], 118 119 disableHaptics: false, 119 120 disableAutoplay: prefersReducedMotion, 121 + kawaii: false, 120 122 }
+4 -1
src/state/preferences/index.tsx
··· 7 7 import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' 8 8 import {Provider as HiddenPostsProvider} from './hidden-posts' 9 9 import {Provider as InAppBrowserProvider} from './in-app-browser' 10 + import {Provider as KawaiiProvider} from './kawaii' 10 11 import {Provider as LanguagesProvider} from './languages' 11 12 12 13 export { ··· 32 33 <InAppBrowserProvider> 33 34 <DisableHapticsProvider> 34 35 <AutoplayProvider> 35 - <DmServiceUrlProvider>{children}</DmServiceUrlProvider> 36 + <DmServiceUrlProvider> 37 + <KawaiiProvider>{children}</KawaiiProvider> 38 + </DmServiceUrlProvider> 36 39 </AutoplayProvider> 37 40 </DisableHapticsProvider> 38 41 </InAppBrowserProvider>
+50
src/state/preferences/kawaii.tsx
··· 1 + import React from 'react' 2 + 3 + import {isWeb} from '#/platform/detection' 4 + import * as persisted from '#/state/persisted' 5 + 6 + type StateContext = persisted.Schema['kawaii'] 7 + 8 + const stateContext = React.createContext<StateContext>( 9 + persisted.defaults.kawaii, 10 + ) 11 + 12 + export function Provider({children}: React.PropsWithChildren<{}>) { 13 + const [state, setState] = React.useState(persisted.get('kawaii')) 14 + 15 + const setStateWrapped = React.useCallback( 16 + (kawaii: persisted.Schema['kawaii']) => { 17 + setState(kawaii) 18 + persisted.write('kawaii', kawaii) 19 + }, 20 + [setState], 21 + ) 22 + 23 + React.useEffect(() => { 24 + return persisted.onUpdate(() => { 25 + setState(persisted.get('kawaii')) 26 + }) 27 + }, [setStateWrapped]) 28 + 29 + React.useEffect(() => { 30 + // dumb and stupid but it's web only so just refresh the page if you want to change it 31 + 32 + if (isWeb) { 33 + const kawaii = new URLSearchParams(window.location.search).get('kawaii') 34 + switch (kawaii) { 35 + case 'true': 36 + setStateWrapped(true) 37 + break 38 + case 'false': 39 + setStateWrapped(false) 40 + break 41 + } 42 + } 43 + }, [setStateWrapped]) 44 + 45 + return <stateContext.Provider value={state}>{children}</stateContext.Provider> 46 + } 47 + 48 + export function useKawaiiMode() { 49 + return React.useContext(stateContext) 50 + }
+9 -4
src/view/com/auth/SplashScreen.web.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 + import {useKawaiiMode} from '#/state/preferences/kawaii' 7 8 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 9 import {Logo} from '#/view/icons/Logo' 9 10 import {Logotype} from '#/view/icons/Logotype' ··· 27 28 const {_} = useLingui() 28 29 const t = useTheme() 29 30 const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries() 31 + 32 + const kawaii = useKawaiiMode() 30 33 31 34 return ( 32 35 <> ··· 66 69 ]}> 67 70 <ErrorBoundary> 68 71 <View style={[a.justify_center, a.align_center]}> 69 - <Logo width={92} fill="sky" /> 72 + <Logo width={kawaii ? 300 : 92} fill="sky" /> 70 73 71 - <View style={[a.pb_sm, a.pt_5xl]}> 72 - <Logotype width={161} fill={t.atoms.text.color} /> 73 - </View> 74 + {!kawaii && ( 75 + <View style={[a.pb_sm, a.pt_5xl]}> 76 + <Logotype width={161} fill={t.atoms.text.color} /> 77 + </View> 78 + )} 74 79 75 80 <Text 76 81 style={[
+12 -2
src/view/com/home/HomeHeaderLayout.web.tsx
··· 15 15 import {usePalette} from 'lib/hooks/usePalette' 16 16 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 17 import {Logo} from '#/view/icons/Logo' 18 + import {useKawaiiMode} from '../../../state/preferences/kawaii' 18 19 import {Link} from '../util/Link' 19 20 import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' 20 21 ··· 43 44 const {hasSession} = useSession() 44 45 const {_} = useLingui() 45 46 47 + const kawaii = useKawaiiMode() 48 + 46 49 return ( 47 50 <> 48 51 {hasSession && ( 49 - <View style={[pal.view, pal.border, styles.bar, styles.topBar]}> 52 + <View 53 + style={[ 54 + pal.view, 55 + pal.border, 56 + styles.bar, 57 + styles.topBar, 58 + kawaii && {paddingTop: 4, paddingBottom: 0}, 59 + ]}> 50 60 <Link 51 61 href="/settings/following-feed" 52 62 hitSlop={10} ··· 58 68 style={pal.textLight as FontAwesomeIconStyle} 59 69 /> 60 70 </Link> 61 - <Logo width={28} /> 71 + <Logo width={kawaii ? 60 : 28} /> 62 72 <Link 63 73 href="/settings/saved-feeds" 64 74 hitSlop={10}
+23 -2
src/view/icons/Logo.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, TextProps} from 'react-native' 3 3 import Svg, { 4 - Path, 5 4 Defs, 6 5 LinearGradient, 6 + Path, 7 + PathProps, 7 8 Stop, 8 9 SvgProps, 9 - PathProps, 10 10 } from 'react-native-svg' 11 + import {Image} from 'expo-image' 11 12 12 13 import {colors} from '#/lib/styles' 14 + import {useKawaiiMode} from '#/state/preferences/kawaii' 13 15 14 16 const ratio = 57 / 64 15 17 ··· 25 27 const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3 26 28 // @ts-ignore it's fiiiiine 27 29 const size = parseInt(rest.width || 32) 30 + 31 + const isKawaii = useKawaiiMode() 32 + 33 + if (isKawaii) { 34 + return ( 35 + <Image 36 + source={ 37 + size > 100 38 + ? require('../../../assets/kawaii.png') 39 + : require('../../../assets/kawaii_smol.png') 40 + } 41 + accessibilityLabel="Bluesky" 42 + accessibilityHint="" 43 + accessibilityIgnoresInvertColors 44 + style={[{height: size, aspectRatio: 1.4}]} 45 + /> 46 + ) 47 + } 48 + 28 49 return ( 29 50 <Svg 30 51 fill="none"
+13
src/view/shell/Drawer.tsx
··· 18 18 import {StackActions, useNavigation} from '@react-navigation/native' 19 19 20 20 import {emitSoftReset} from '#/state/events' 21 + import {useKawaiiMode} from '#/state/preferences/kawaii' 21 22 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 22 23 import {useProfileQuery} from '#/state/queries/profile' 23 24 import {SessionAccount, useSession} from '#/state/session' ··· 117 118 const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} = 118 119 useNavigationTabState() 119 120 const {hasSession, currentAccount} = useSession() 121 + const kawaii = useKawaiiMode() 120 122 121 123 // events 122 124 // = ··· 262 264 href="https://bsky.social/about/support/privacy-policy" 263 265 text={_(msg`Privacy Policy`)} 264 266 /> 267 + {kawaii && ( 268 + <Text type="md" style={pal.textLight}> 269 + Logo by{' '} 270 + <TextLink 271 + type="md" 272 + href="/profile/sawaratsuki.bsky.social" 273 + text="@sawaratsuki.bsky.social" 274 + style={pal.link} 275 + /> 276 + </Text> 277 + )} 265 278 </View> 266 279 267 280 <View style={styles.smallSpacer} />
+24 -9
src/view/shell/desktop/RightNav.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 - import {usePalette} from 'lib/hooks/usePalette' 4 - import {DesktopSearch} from './Search' 5 - import {DesktopFeeds} from './Feeds' 6 - import {Text} from 'view/com/util/text/Text' 7 - import {TextLink} from 'view/com/util/Link' 8 - import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' 9 - import {s} from 'lib/styles' 10 - import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 3 + import {msg} from '@lingui/macro' 11 4 import {useLingui} from '@lingui/react' 12 - import {msg} from '@lingui/macro' 5 + 6 + import {useKawaiiMode} from '#/state/preferences/kawaii' 13 7 import {useSession} from '#/state/session' 8 + import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' 9 + import {usePalette} from 'lib/hooks/usePalette' 10 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 11 + import {s} from 'lib/styles' 12 + import {TextLink} from 'view/com/util/Link' 13 + import {Text} from 'view/com/util/text/Text' 14 + import {DesktopFeeds} from './Feeds' 15 + import {DesktopSearch} from './Search' 14 16 15 17 export function DesktopRightNav({routeName}: {routeName: string}) { 16 18 const pal = usePalette('default') 17 19 const {_} = useLingui() 18 20 const {hasSession, currentAccount} = useSession() 21 + 22 + const kawaii = useKawaiiMode() 19 23 20 24 const {isTablet} = useWebMediaQueries() 21 25 if (isTablet) { ··· 90 94 text={_(msg`Help`)} 91 95 /> 92 96 </View> 97 + {kawaii && ( 98 + <Text type="md" style={[pal.textLight, {marginTop: 12}]}> 99 + Logo by{' '} 100 + <TextLink 101 + type="md" 102 + href="/profile/sawaratsuki.bsky.social" 103 + text="@sawaratsuki.bsky.social" 104 + style={pal.link} 105 + /> 106 + </Text> 107 + )} 93 108 </View> 94 109 </View> 95 110 </View>