Add kawaii mode (#3773)

zio/stable
Samuel Newman 2024-05-01 08:59:40 +01:00 committed by GitHub
parent 181e61bedb
commit 81ae7e425d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 152 additions and 30 deletions

View File

@ -20,8 +20,8 @@ jobs:
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.21' go-version: '1.21'
- name: Dummy JS File - name: Dummy Static Files
run: touch bskyweb/static/js/blah.js run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
- name: Check - name: Check
run: cd bskyweb/ && make check run: cd bskyweb/ && make check
- name: Build (binary) - name: Build (binary)
@ -37,7 +37,7 @@ jobs:
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.21' go-version: '1.21'
- name: Dummy JS File - name: Dummy Static Files
run: touch bskyweb/static/js/blah.js run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt
- name: Lint - name: Lint
run: cd bskyweb/ && make lint run: cd bskyweb/ && make lint

View File

@ -40,6 +40,7 @@ RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bs
# hack around issue with empty directory and go:embed # hack around issue with empty directory and go:embed
RUN touch bskyweb/static/js/empty.txt RUN touch bskyweb/static/js/empty.txt
RUN touch bskyweb/static/media/empty.txt
# #
# Generate the embedr Go binary. # Generate the embedr Go binary.

BIN
assets/kawaii.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

2
bskyweb/.gitignore vendored
View File

@ -10,6 +10,8 @@ static/js/*.js
static/js/*.map static/js/*.map
static/js/*.js.LICENSE.txt static/js/*.js.LICENSE.txt
static/js/empty.txt static/js/empty.txt
static/media/*.png
static/media/empty.txt
templates/scripts.html templates/scripts.html
templates/*-embed.html templates/*-embed.html
static/embed/*.html static/embed/*.html

View File

@ -158,7 +158,7 @@ func serve(cctx *cli.Context) error {
// Cache javascript and images files for 1 week, which works because // Cache javascript and images files for 1 week, which works because
// they're always versioned (e.g. /static/js/main.64c14927.js) // they're always versioned (e.g. /static/js/main.64c14927.js)
if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") { if strings.HasPrefix(path, "/static/js/") || strings.HasPrefix(path, "/static/images/") || strings.HasPrefix(path, "/static/media/") {
maxAge = 7 * (60 * 60 * 24) // 1 week maxAge = 7 * (60 * 60 * 24) // 1 week
} }

View File

View File

@ -15,7 +15,7 @@
"web": "expo start --web", "web": "expo start --web",
"use-build-number": "./scripts/useBuildNumberEnv.sh", "use-build-number": "./scripts/useBuildNumberEnv.sh",
"use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh", "use-build-number-with-bump": "./scripts/useBuildNumberEnvWithBump.sh",
"build-web": "expo export:web && node ./scripts/post-web-build.js && cp -v ./web-build/static/js/*.* ./bskyweb/static/js/", "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/",
"build-all": "yarn intl:build && yarn use-build-number-with-bump eas build --platform all", "build-all": "yarn intl:build && yarn use-build-number-with-bump eas build --platform all",
"build-ios": "yarn use-build-number-with-bump eas build -p ios", "build-ios": "yarn use-build-number-with-bump eas build -p ios",
"build-android": "yarn use-build-number-with-bump eas build -p android", "build-android": "yarn use-build-number-with-bump eas build -p android",

View File

@ -1,11 +1,11 @@
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import {logger} from '#/logger'
import {defaults, Schema} from '#/state/persisted/schema'
import {migrate} from '#/state/persisted/legacy'
import * as store from '#/state/persisted/store'
import BroadcastChannel from '#/lib/broadcast'
export type {Schema, PersistedAccount} from '#/state/persisted/schema' import BroadcastChannel from '#/lib/broadcast'
import {logger} from '#/logger'
import {migrate} from '#/state/persisted/legacy'
import {defaults, Schema} from '#/state/persisted/schema'
import * as store from '#/state/persisted/store'
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
export {defaults} from '#/state/persisted/schema' export {defaults} from '#/state/persisted/schema'
const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL') const broadcast = new BroadcastChannel('BSKY_BROADCAST_CHANNEL')

View File

@ -80,6 +80,7 @@ export const schema = z.object({
pdsAddressHistory: z.array(z.string()).optional(), pdsAddressHistory: z.array(z.string()).optional(),
disableHaptics: z.boolean().optional(), disableHaptics: z.boolean().optional(),
disableAutoplay: z.boolean().optional(), disableAutoplay: z.boolean().optional(),
kawaii: z.boolean().optional(),
}) })
export type Schema = z.infer<typeof schema> export type Schema = z.infer<typeof schema>
@ -117,4 +118,5 @@ export const defaults: Schema = {
pdsAddressHistory: [], pdsAddressHistory: [],
disableHaptics: false, disableHaptics: false,
disableAutoplay: prefersReducedMotion, disableAutoplay: prefersReducedMotion,
kawaii: false,
} }

View File

@ -7,6 +7,7 @@ import {Provider as DisableHapticsProvider} from './disable-haptics'
import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs'
import {Provider as HiddenPostsProvider} from './hidden-posts' import {Provider as HiddenPostsProvider} from './hidden-posts'
import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as InAppBrowserProvider} from './in-app-browser'
import {Provider as KawaiiProvider} from './kawaii'
import {Provider as LanguagesProvider} from './languages' import {Provider as LanguagesProvider} from './languages'
export { export {
@ -32,7 +33,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
<InAppBrowserProvider> <InAppBrowserProvider>
<DisableHapticsProvider> <DisableHapticsProvider>
<AutoplayProvider> <AutoplayProvider>
<DmServiceUrlProvider>{children}</DmServiceUrlProvider> <DmServiceUrlProvider>
<KawaiiProvider>{children}</KawaiiProvider>
</DmServiceUrlProvider>
</AutoplayProvider> </AutoplayProvider>
</DisableHapticsProvider> </DisableHapticsProvider>
</InAppBrowserProvider> </InAppBrowserProvider>

View File

@ -0,0 +1,50 @@
import React from 'react'
import {isWeb} from '#/platform/detection'
import * as persisted from '#/state/persisted'
type StateContext = persisted.Schema['kawaii']
const stateContext = React.createContext<StateContext>(
persisted.defaults.kawaii,
)
export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, setState] = React.useState(persisted.get('kawaii'))
const setStateWrapped = React.useCallback(
(kawaii: persisted.Schema['kawaii']) => {
setState(kawaii)
persisted.write('kawaii', kawaii)
},
[setState],
)
React.useEffect(() => {
return persisted.onUpdate(() => {
setState(persisted.get('kawaii'))
})
}, [setStateWrapped])
React.useEffect(() => {
// dumb and stupid but it's web only so just refresh the page if you want to change it
if (isWeb) {
const kawaii = new URLSearchParams(window.location.search).get('kawaii')
switch (kawaii) {
case 'true':
setStateWrapped(true)
break
case 'false':
setStateWrapped(false)
break
}
}
}, [setStateWrapped])
return <stateContext.Provider value={state}>{children}</stateContext.Provider>
}
export function useKawaiiMode() {
return React.useContext(stateContext)
}

View File

@ -4,6 +4,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
import {Logotype} from '#/view/icons/Logotype' import {Logotype} from '#/view/icons/Logotype'
@ -28,6 +29,8 @@ export const SplashScreen = ({
const t = useTheme() const t = useTheme()
const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries() const {isTabletOrMobile: isMobileWeb} = useWebMediaQueries()
const kawaii = useKawaiiMode()
return ( return (
<> <>
{onDismiss && ( {onDismiss && (
@ -66,11 +69,13 @@ export const SplashScreen = ({
]}> ]}>
<ErrorBoundary> <ErrorBoundary>
<View style={[a.justify_center, a.align_center]}> <View style={[a.justify_center, a.align_center]}>
<Logo width={92} fill="sky" /> <Logo width={kawaii ? 300 : 92} fill="sky" />
{!kawaii && (
<View style={[a.pb_sm, a.pt_5xl]}> <View style={[a.pb_sm, a.pt_5xl]}>
<Logotype width={161} fill={t.atoms.text.color} /> <Logotype width={161} fill={t.atoms.text.color} />
</View> </View>
)}
<Text <Text
style={[ style={[

View File

@ -15,6 +15,7 @@ import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
import {useKawaiiMode} from '../../../state/preferences/kawaii'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile' import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
@ -43,10 +44,19 @@ function HomeHeaderLayoutDesktopAndTablet({
const {hasSession} = useSession() const {hasSession} = useSession()
const {_} = useLingui() const {_} = useLingui()
const kawaii = useKawaiiMode()
return ( return (
<> <>
{hasSession && ( {hasSession && (
<View style={[pal.view, pal.border, styles.bar, styles.topBar]}> <View
style={[
pal.view,
pal.border,
styles.bar,
styles.topBar,
kawaii && {paddingTop: 4, paddingBottom: 0},
]}>
<Link <Link
href="/settings/following-feed" href="/settings/following-feed"
hitSlop={10} hitSlop={10}
@ -58,7 +68,7 @@ function HomeHeaderLayoutDesktopAndTablet({
style={pal.textLight as FontAwesomeIconStyle} style={pal.textLight as FontAwesomeIconStyle}
/> />
</Link> </Link>
<Logo width={28} /> <Logo width={kawaii ? 60 : 28} />
<Link <Link
href="/settings/saved-feeds" href="/settings/saved-feeds"
hitSlop={10} hitSlop={10}

View File

@ -1,15 +1,17 @@
import React from 'react' import React from 'react'
import {StyleSheet, TextProps} from 'react-native' import {StyleSheet, TextProps} from 'react-native'
import Svg, { import Svg, {
Path,
Defs, Defs,
LinearGradient, LinearGradient,
Path,
PathProps,
Stop, Stop,
SvgProps, SvgProps,
PathProps,
} from 'react-native-svg' } from 'react-native-svg'
import {Image} from 'expo-image'
import {colors} from '#/lib/styles' import {colors} from '#/lib/styles'
import {useKawaiiMode} from '#/state/preferences/kawaii'
const ratio = 57 / 64 const ratio = 57 / 64
@ -25,6 +27,25 @@ export const Logo = React.forwardRef(function LogoImpl(props: Props, ref) {
const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3 const _fill = gradient ? 'url(#sky)' : fill || styles?.color || colors.blue3
// @ts-ignore it's fiiiiine // @ts-ignore it's fiiiiine
const size = parseInt(rest.width || 32) const size = parseInt(rest.width || 32)
const isKawaii = useKawaiiMode()
if (isKawaii) {
return (
<Image
source={
size > 100
? require('../../../assets/kawaii.png')
: require('../../../assets/kawaii_smol.png')
}
accessibilityLabel="Bluesky"
accessibilityHint=""
accessibilityIgnoresInvertColors
style={[{height: size, aspectRatio: 1.4}]}
/>
)
}
return ( return (
<Svg <Svg
fill="none" fill="none"

View File

@ -18,6 +18,7 @@ import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {SessionAccount, useSession} from '#/state/session' import {SessionAccount, useSession} from '#/state/session'
@ -117,6 +118,7 @@ let DrawerContent = ({}: {}): React.ReactNode => {
const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} = const {isAtHome, isAtSearch, isAtFeeds, isAtNotifications, isAtMyProfile} =
useNavigationTabState() useNavigationTabState()
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const kawaii = useKawaiiMode()
// events // events
// = // =
@ -262,6 +264,17 @@ let DrawerContent = ({}: {}): React.ReactNode => {
href="https://bsky.social/about/support/privacy-policy" href="https://bsky.social/about/support/privacy-policy"
text={_(msg`Privacy Policy`)} text={_(msg`Privacy Policy`)}
/> />
{kawaii && (
<Text type="md" style={pal.textLight}>
Logo by{' '}
<TextLink
type="md"
href="/profile/sawaratsuki.bsky.social"
text="@sawaratsuki.bsky.social"
style={pal.link}
/>
</Text>
)}
</View> </View>
<View style={styles.smallSpacer} /> <View style={styles.smallSpacer} />

View File

@ -1,22 +1,26 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {DesktopSearch} from './Search'
import {DesktopFeeds} from './Feeds'
import {Text} from 'view/com/util/text/Text'
import {TextLink} from 'view/com/util/Link'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
import {s} from 'lib/styles'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useKawaiiMode} from '#/state/preferences/kawaii'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {TextLink} from 'view/com/util/Link'
import {Text} from 'view/com/util/text/Text'
import {DesktopFeeds} from './Feeds'
import {DesktopSearch} from './Search'
export function DesktopRightNav({routeName}: {routeName: string}) { export function DesktopRightNav({routeName}: {routeName: string}) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const kawaii = useKawaiiMode()
const {isTablet} = useWebMediaQueries() const {isTablet} = useWebMediaQueries()
if (isTablet) { if (isTablet) {
return null return null
@ -90,6 +94,17 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
text={_(msg`Help`)} text={_(msg`Help`)}
/> />
</View> </View>
{kawaii && (
<Text type="md" style={[pal.textLight, {marginTop: 12}]}>
Logo by{' '}
<TextLink
type="md"
href="/profile/sawaratsuki.bsky.social"
text="@sawaratsuki.bsky.social"
style={pal.link}
/>
</Text>
)}
</View> </View>
</View> </View>
</View> </View>