diff --git a/app.config.js b/app.config.js
index ecc7e4d40c..fe65c4fde5 100644
--- a/app.config.js
+++ b/app.config.js
@@ -230,6 +230,31 @@ module.exports = function (config) {
'./plugins/shareExtension/withShareExtensions.js',
'./plugins/notificationsExtension/withNotificationsExtension.js',
'./plugins/withAppDelegateReferrer.js',
+ [
+ 'expo-font',
+ {
+ fonts: [
+ // './assets/fonts/inter/Inter-Thin.otf',
+ // './assets/fonts/inter/Inter-ThinItalic.otf',
+ // './assets/fonts/inter/Inter-ExtraLight.otf',
+ // './assets/fonts/inter/Inter-ExtraLightItalic.otf',
+ // './assets/fonts/inter/Inter-Light.otf',
+ // './assets/fonts/inter/Inter-LightItalic.otf',
+ './assets/fonts/inter/Inter-Regular.otf',
+ './assets/fonts/inter/Inter-Italic.otf',
+ './assets/fonts/inter/Inter-Medium.otf',
+ './assets/fonts/inter/Inter-MediumItalic.otf',
+ './assets/fonts/inter/Inter-SemiBold.otf',
+ './assets/fonts/inter/Inter-SemiBoldItalic.otf',
+ './assets/fonts/inter/Inter-Bold.otf',
+ './assets/fonts/inter/Inter-BoldItalic.otf',
+ './assets/fonts/inter/Inter-ExtraBold.otf',
+ './assets/fonts/inter/Inter-ExtraBoldItalic.otf',
+ './assets/fonts/inter/Inter-Black.otf',
+ './assets/fonts/inter/Inter-BlackItalic.otf',
+ ],
+ },
+ ],
].filter(Boolean),
extra: {
eas: {
diff --git a/assets/fonts/inter/Inter-Black.otf b/assets/fonts/inter/Inter-Black.otf
new file mode 100644
index 0000000000..44d1779af6
Binary files /dev/null and b/assets/fonts/inter/Inter-Black.otf differ
diff --git a/assets/fonts/inter/Inter-BlackItalic.otf b/assets/fonts/inter/Inter-BlackItalic.otf
new file mode 100644
index 0000000000..6fc475e415
Binary files /dev/null and b/assets/fonts/inter/Inter-BlackItalic.otf differ
diff --git a/assets/fonts/inter/Inter-Bold.otf b/assets/fonts/inter/Inter-Bold.otf
new file mode 100644
index 0000000000..58a38073e8
Binary files /dev/null and b/assets/fonts/inter/Inter-Bold.otf differ
diff --git a/assets/fonts/inter/Inter-BoldItalic.otf b/assets/fonts/inter/Inter-BoldItalic.otf
new file mode 100644
index 0000000000..e67935aa5a
Binary files /dev/null and b/assets/fonts/inter/Inter-BoldItalic.otf differ
diff --git a/assets/fonts/inter/Inter-ExtraBold.otf b/assets/fonts/inter/Inter-ExtraBold.otf
new file mode 100644
index 0000000000..66cd95228c
Binary files /dev/null and b/assets/fonts/inter/Inter-ExtraBold.otf differ
diff --git a/assets/fonts/inter/Inter-ExtraBoldItalic.otf b/assets/fonts/inter/Inter-ExtraBoldItalic.otf
new file mode 100644
index 0000000000..f269814a64
Binary files /dev/null and b/assets/fonts/inter/Inter-ExtraBoldItalic.otf differ
diff --git a/assets/fonts/inter/Inter-ExtraLight.otf b/assets/fonts/inter/Inter-ExtraLight.otf
new file mode 100644
index 0000000000..b603db3c77
Binary files /dev/null and b/assets/fonts/inter/Inter-ExtraLight.otf differ
diff --git a/assets/fonts/inter/Inter-ExtraLightItalic.otf b/assets/fonts/inter/Inter-ExtraLightItalic.otf
new file mode 100644
index 0000000000..f65051941c
Binary files /dev/null and b/assets/fonts/inter/Inter-ExtraLightItalic.otf differ
diff --git a/assets/fonts/inter/Inter-Italic.otf b/assets/fonts/inter/Inter-Italic.otf
new file mode 100644
index 0000000000..f78848b987
Binary files /dev/null and b/assets/fonts/inter/Inter-Italic.otf differ
diff --git a/assets/fonts/inter/Inter-Light.otf b/assets/fonts/inter/Inter-Light.otf
new file mode 100644
index 0000000000..7da794bd3b
Binary files /dev/null and b/assets/fonts/inter/Inter-Light.otf differ
diff --git a/assets/fonts/inter/Inter-LightItalic.otf b/assets/fonts/inter/Inter-LightItalic.otf
new file mode 100644
index 0000000000..32ef937c52
Binary files /dev/null and b/assets/fonts/inter/Inter-LightItalic.otf differ
diff --git a/assets/fonts/inter/Inter-Medium.otf b/assets/fonts/inter/Inter-Medium.otf
new file mode 100644
index 0000000000..f44f89adac
Binary files /dev/null and b/assets/fonts/inter/Inter-Medium.otf differ
diff --git a/assets/fonts/inter/Inter-MediumItalic.otf b/assets/fonts/inter/Inter-MediumItalic.otf
new file mode 100644
index 0000000000..1970f57214
Binary files /dev/null and b/assets/fonts/inter/Inter-MediumItalic.otf differ
diff --git a/assets/fonts/inter/Inter-Regular.otf b/assets/fonts/inter/Inter-Regular.otf
new file mode 100644
index 0000000000..2d0bd1d645
Binary files /dev/null and b/assets/fonts/inter/Inter-Regular.otf differ
diff --git a/assets/fonts/inter/Inter-SemiBold.otf b/assets/fonts/inter/Inter-SemiBold.otf
new file mode 100644
index 0000000000..52c84550ba
Binary files /dev/null and b/assets/fonts/inter/Inter-SemiBold.otf differ
diff --git a/assets/fonts/inter/Inter-SemiBoldItalic.otf b/assets/fonts/inter/Inter-SemiBoldItalic.otf
new file mode 100644
index 0000000000..b725bfc883
Binary files /dev/null and b/assets/fonts/inter/Inter-SemiBoldItalic.otf differ
diff --git a/assets/fonts/inter/Inter-Thin.otf b/assets/fonts/inter/Inter-Thin.otf
new file mode 100644
index 0000000000..568a18560c
Binary files /dev/null and b/assets/fonts/inter/Inter-Thin.otf differ
diff --git a/assets/fonts/inter/Inter-ThinItalic.otf b/assets/fonts/inter/Inter-ThinItalic.otf
new file mode 100644
index 0000000000..c5ed37c358
Binary files /dev/null and b/assets/fonts/inter/Inter-ThinItalic.otf differ
diff --git a/assets/icons/textSize_stroke2_corner0_rounded.svg b/assets/icons/textSize_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..6c7537d100
--- /dev/null
+++ b/assets/icons/textSize_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/titleCase_stroke2_corner0_rounded.svg b/assets/icons/titleCase_stroke2_corner0_rounded.svg
new file mode 100644
index 0000000000..facdfc0e16
--- /dev/null
+++ b/assets/icons/titleCase_stroke2_corner0_rounded.svg
@@ -0,0 +1 @@
+
diff --git a/package.json b/package.json
index 93788deb46..ba7882902c 100644
--- a/package.json
+++ b/package.json
@@ -124,6 +124,7 @@
"expo-dev-client": "^4.0.14",
"expo-device": "~6.0.2",
"expo-file-system": "^17.0.1",
+ "expo-font": "~12.0.10",
"expo-haptics": "^13.0.1",
"expo-image": "~1.12.9",
"expo-image-manipulator": "^12.0.5",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 2ec666e2cc..9214253aca 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -55,7 +55,7 @@ import {TestCtrls} from '#/view/com/testing/TestCtrls'
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
import * as Toast from '#/view/com/util/Toast'
import {Shell} from '#/view/shell'
-import {ThemeProvider as Alf} from '#/alf'
+import {ThemeProvider as Alf, useFonts} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
@@ -106,62 +106,60 @@ function InnerApp() {
}, [_])
return (
-
-
-
-
-
-
+
+
+
+
+
+
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
)
}
function App() {
const [isReady, setReady] = useState(false)
+ const [loaded] = useFonts()
React.useEffect(() => {
initPersistedState().then(() => setReady(true))
}, [])
- if (!isReady) {
+ if (!isReady || !loaded) {
return null
}
diff --git a/src/App.web.tsx b/src/App.web.tsx
index 6efe7cc022..1c66507336 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -46,7 +46,7 @@ import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/Video
import * as Toast from '#/view/com/util/Toast'
import {ToastContainer} from '#/view/com/util/Toast.web'
import {Shell} from '#/view/shell/index'
-import {ThemeProvider as Alf} from '#/alf'
+import {ThemeProvider as Alf, useFonts} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
@@ -96,62 +96,61 @@ function InnerApp() {
return (
-
-
-
-
-
-
+
+
+
+
+
+
-
-
- {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
}
function App() {
const [isReady, setReady] = useState(false)
+ const [loaded] = useFonts()
React.useEffect(() => {
initPersistedState().then(() => setReady(true))
}, [])
- if (!isReady) {
+ if (!isReady || !loaded) {
return null
}
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index d2e7ffc2ee..9f75d305ae 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -225,43 +225,43 @@ export const atoms = {
},
text_2xs: {
fontSize: tokens.fontSize._2xs,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_xs: {
fontSize: tokens.fontSize.xs,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_sm: {
fontSize: tokens.fontSize.sm,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_md: {
fontSize: tokens.fontSize.md,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_lg: {
fontSize: tokens.fontSize.lg,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_xl: {
fontSize: tokens.fontSize.xl,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_2xl: {
fontSize: tokens.fontSize._2xl,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_3xl: {
fontSize: tokens.fontSize._3xl,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_4xl: {
fontSize: tokens.fontSize._4xl,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
text_5xl: {
fontSize: tokens.fontSize._5xl,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
leading_tight: {
lineHeight: 1.15,
@@ -273,10 +273,7 @@ export const atoms = {
lineHeight: 1.5,
},
tracking_normal: {
- letterSpacing: 0,
- },
- tracking_wide: {
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
},
font_normal: {
fontWeight: tokens.fontWeight.normal,
diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts
new file mode 100644
index 0000000000..ce658fa05b
--- /dev/null
+++ b/src/alf/fonts.ts
@@ -0,0 +1,111 @@
+import {useFonts as defaultUseFonts} from 'expo-font'
+
+import {isNative, isWeb} from '#/platform/detection'
+import {Device, device} from '#/storage'
+
+const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
+
+const factor = 0.0625 // 1 - (15/16)
+const fontScaleMultipliers: Record = {
+ '-2': 1 - factor * 3,
+ '-1': 1 - factor * 2,
+ '0': 1 - factor * 1, // default
+ '1': 1,
+ '2': 1 + factor * 1,
+}
+
+export function computeFontScaleMultiplier(scale: Device['fontScale']) {
+ return fontScaleMultipliers[scale]
+}
+
+export function getFontScale() {
+ return device.get(['fontScale']) ?? '0'
+}
+
+export function setFontScale(fontScale: Device['fontScale']) {
+ device.set(['fontScale'], fontScale)
+}
+
+export function getFontFamily() {
+ return device.get(['fontFamily']) || 'theme'
+}
+
+export function setFontFamily(fontFamily: Device['fontFamily']) {
+ device.set(['fontFamily'], fontFamily)
+}
+
+/*
+ * Unused fonts are commented out, but the files are there if we need them.
+ */
+export function useFonts() {
+ /**
+ * For native, the `expo-font` config plugin embeds the fonts in the
+ * application binary. But `expo-font` isn't supported on web, so we fall
+ * back to async loading here.
+ */
+ if (isNative) return [true, null]
+ return defaultUseFonts({
+ // 'Inter-Thin': require('../../assets/fonts/inter/Inter-Thin.otf'),
+ // 'Inter-ThinItalic': require('../../assets/fonts/inter/Inter-ThinItalic.otf'),
+ // 'Inter-ExtraLight': require('../../assets/fonts/inter/Inter-ExtraLight.otf'),
+ // 'Inter-ExtraLightItalic': require('../../assets/fonts/inter/Inter-ExtraLightItalic.otf'),
+ // 'Inter-Light': require('../../assets/fonts/inter/Inter-Light.otf'),
+ // 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
+ 'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
+ 'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
+ 'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
+ 'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
+ 'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
+ 'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
+ 'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
+ 'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
+ 'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
+ 'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
+ 'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
+ 'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
+ })
+}
+
+/*
+ * Unused fonts are commented out, but the files are there if we need them.
+ */
+export function applyFonts(
+ style: Record,
+ fontFamily: 'system' | 'theme',
+) {
+ if (fontFamily === 'theme') {
+ style.fontFamily =
+ {
+ // '100': 'Inter-Thin',
+ // '200': 'Inter-ExtraLight',
+ // '300': 'Inter-Light',
+ '100': 'Inter-Regular',
+ '200': 'Inter-Regular',
+ '300': 'Inter-Regular',
+ '400': 'Inter-Regular',
+ '500': 'Inter-Medium',
+ '600': 'Inter-SemiBold',
+ '700': 'Inter-Bold',
+ '800': 'Inter-ExtraBold',
+ '900': 'Inter-Black',
+ }[style.fontWeight as string] || 'Inter-Regular'
+
+ if (style.fontStyle === 'italic') {
+ if (style.fontFamily === 'Inter-Regular') {
+ style.fontFamily = 'Inter-Italic'
+ } else {
+ style.fontFamily += 'Italic'
+ }
+ }
+
+ // fallback families only supported on web
+ if (isWeb) {
+ style.fontFamily += `, ${FAMILIES}`
+ }
+ } else {
+ // fallback families only supported on web
+ if (isWeb) {
+ style.fontFamily = style.fontFamily || FAMILIES
+ }
+ }
+}
diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index d699de6a5b..f9d93d4ca8 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -1,25 +1,47 @@
import React from 'react'
import {useMediaQuery} from 'react-responsive'
+import {
+ computeFontScaleMultiplier,
+ getFontFamily,
+ getFontScale,
+ setFontFamily as persistFontFamily,
+ setFontScale as persistFontScale,
+} from '#/alf/fonts'
import {createThemes, defaultTheme} from '#/alf/themes'
import {Theme, ThemeName} from '#/alf/types'
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
+import {Device} from '#/storage'
export {atoms} from '#/alf/atoms'
+export * from '#/alf/fonts'
export * as tokens from '#/alf/tokens'
export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
-/*
- * Context
- */
-export const Context = React.createContext<{
+export type Alf = {
themeName: ThemeName
theme: Theme
themes: ReturnType
-}>({
+ fonts: {
+ scale: Exclude
+ scaleMultiplier: number
+ family: Device['fontFamily']
+ setFontScale: (fontScale: Exclude) => void
+ setFontFamily: (fontFamily: Device['fontFamily']) => void
+ }
+ /**
+ * Feature flags or other gated options
+ */
+ flags: {}
+}
+
+/*
+ * Context
+ */
+export const Context = React.createContext({
themeName: 'light',
theme: defaultTheme,
themes: createThemes({
@@ -29,12 +51,48 @@ export const Context = React.createContext<{
positive: GREEN_HUE,
},
}),
+ fonts: {
+ scale: getFontScale(),
+ scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
+ family: getFontFamily(),
+ setFontScale: () => {},
+ setFontFamily: () => {},
+ },
+ flags: {},
})
export function ThemeProvider({
children,
theme: themeName,
}: React.PropsWithChildren<{theme: ThemeName}>) {
+ const [fontScale, setFontScale] = React.useState(() =>
+ getFontScale(),
+ )
+ const [fontScaleMultiplier, setFontScaleMultiplier] = React.useState(() =>
+ computeFontScaleMultiplier(fontScale),
+ )
+ const setFontScaleAndPersist = React.useCallback<
+ Alf['fonts']['setFontScale']
+ >(
+ fontScale => {
+ setFontScale(fontScale)
+ persistFontScale(fontScale)
+ setFontScaleMultiplier(computeFontScaleMultiplier(fontScale))
+ },
+ [setFontScale],
+ )
+ const [fontFamily, setFontFamily] = React.useState(
+ () => getFontFamily(),
+ )
+ const setFontFamilyAndPersist = React.useCallback<
+ Alf['fonts']['setFontFamily']
+ >(
+ fontFamily => {
+ setFontFamily(fontFamily)
+ persistFontFamily(fontFamily)
+ },
+ [setFontFamily],
+ )
const themes = React.useMemo(() => {
return createThemes({
hues: {
@@ -44,28 +102,47 @@ export function ThemeProvider({
},
})
}, [])
- const theme = themes[themeName]
return (
(
() => ({
themes,
themeName: themeName,
- theme: theme,
+ theme: themes[themeName],
+ fonts: {
+ scale: fontScale,
+ scaleMultiplier: fontScaleMultiplier,
+ family: fontFamily,
+ setFontScale: setFontScaleAndPersist,
+ setFontFamily: setFontFamilyAndPersist,
+ },
+ flags: {},
}),
- [theme, themeName, themes],
+ [
+ themeName,
+ themes,
+ fontScale,
+ setFontScaleAndPersist,
+ fontFamily,
+ setFontFamilyAndPersist,
+ fontScaleMultiplier,
+ ],
)}>
{children}
)
}
+export function useAlf() {
+ return React.useContext(Context)
+}
+
export function useTheme(theme?: ThemeName) {
- const ctx = React.useContext(Context)
+ const alf = useAlf()
return React.useMemo(() => {
- return theme ? ctx.themes[theme] : ctx.theme
- }, [theme, ctx])
+ return theme ? alf.themes[theme] : alf.theme
+ }, [theme, alf])
}
export function useBreakpoints() {
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 0208945eeb..d43d2b67dd 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -1,3 +1,7 @@
+import {Platform} from 'react-native'
+
+export const TRACKING = Platform.OS === 'android' ? 0.1 : 0
+
export const color = {
temp_purple: 'rgb(105 0 255)',
temp_purple_dark: 'rgb(83 0 202)',
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index d65444e1f7..704aa9d987 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -7,7 +7,6 @@ import {
PressableProps,
StyleProp,
StyleSheet,
- Text,
TextProps,
TextStyle,
View,
@@ -17,7 +16,7 @@ import {LinearGradient} from 'expo-linear-gradient'
import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf'
import {Props as SVGIconProps} from '#/components/icons/common'
-import {normalizeTextStyles} from '#/components/Typography'
+import {Text} from '#/components/Typography'
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
export type ButtonColor =
@@ -635,14 +634,7 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
const textStyles = useSharedButtonTextStyles()
return (
-
+
{children}
)
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index cdce3765f0..d5d92048ad 100644
--- a/src/components/Dialog/index.tsx
+++ b/src/components/Dialog/index.tsx
@@ -37,6 +37,7 @@ import {Portal} from '#/components/Portal'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
+export * from '#/components/Dialog/utils'
// @ts-ignore
export const Input = createInput(BottomSheetTextInput)
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index aff1842f77..bf20bd2956 100644
--- a/src/components/Dialog/index.web.tsx
+++ b/src/components/Dialog/index.web.tsx
@@ -27,6 +27,7 @@ import {Portal} from '#/components/Portal'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
+export * from '#/components/Dialog/utils'
export {Input} from '#/components/forms/TextField'
const stopPropagation = (e: any) => e.stopPropagation()
diff --git a/src/components/Dialog/utils.ts b/src/components/Dialog/utils.ts
new file mode 100644
index 0000000000..058d6e8041
--- /dev/null
+++ b/src/components/Dialog/utils.ts
@@ -0,0 +1,18 @@
+import React from 'react'
+
+import {DialogControlProps} from '#/components/Dialog/types'
+
+export function useAutoOpen(control: DialogControlProps, showTimeout?: number) {
+ React.useEffect(() => {
+ if (showTimeout) {
+ const timeout = setTimeout(() => {
+ control.open()
+ }, showTimeout)
+ return () => {
+ clearTimeout(timeout)
+ }
+ } else {
+ control.open()
+ }
+ }, [control, showTimeout])
+}
diff --git a/src/components/Typography.tsx b/src/components/Typography.tsx
index 31dd931c6a..15f88468a7 100644
--- a/src/components/Typography.tsx
+++ b/src/components/Typography.tsx
@@ -3,7 +3,7 @@ import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
import {UITextView} from 'react-native-uitextview'
import {isNative} from '#/platform/detection'
-import {atoms, flatten, useTheme, web} from '#/alf'
+import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
export type TextProps = RNTextProps & {
/**
@@ -34,19 +34,30 @@ export function leading<
* If the `lineHeight` value is > 2, we assume it's an absolute value and
* returns it as-is.
*/
-export function normalizeTextStyles(styles: StyleProp) {
+export function normalizeTextStyles(
+ styles: StyleProp,
+ {
+ fontScale,
+ fontFamily,
+ }: {
+ fontScale: number
+ fontFamily: Alf['fonts']['family']
+ } & Pick,
+) {
const s = flatten(styles)
// should always be defined on these components
- const fontSize = s.fontSize || atoms.text_md.fontSize
+ s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
if (s?.lineHeight) {
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
- s.lineHeight = Math.round(fontSize * s.lineHeight)
+ s.lineHeight = Math.round(s.fontSize * s.lineHeight)
}
} else if (!isNative) {
s.lineHeight = s.fontSize
}
+ applyFonts(s, fontFamily)
+
return s
}
@@ -54,8 +65,13 @@ export function normalizeTextStyles(styles: StyleProp) {
* Our main text component. Use this most of the time.
*/
export function Text({style, selectable, ...rest}: TextProps) {
+ const {fonts, flags} = useAlf()
const t = useTheme()
- const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
+ const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)], {
+ fontScale: fonts.scaleMultiplier,
+ fontFamily: fonts.family,
+ flags,
+ })
return
}
diff --git a/src/components/dialogs/nuxs/NeueTypography.tsx b/src/components/dialogs/nuxs/NeueTypography.tsx
new file mode 100644
index 0000000000..f33cea8e78
--- /dev/null
+++ b/src/components/dialogs/nuxs/NeueTypography.tsx
@@ -0,0 +1,119 @@
+import React from 'react'
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {AppearanceToggleButtonGroup} from '#/screens/Settings/AppearanceSettings'
+import {atoms as a, useAlf, useTheme} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {useNuxDialogContext} from '#/components/dialogs/nuxs'
+import {Divider} from '#/components/Divider'
+import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
+import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
+import {Text} from '#/components/Typography'
+
+export function NeueTypography() {
+ const t = useTheme()
+ const {_} = useLingui()
+ const nuxDialogs = useNuxDialogContext()
+ const control = Dialog.useDialogControl()
+ const {fonts} = useAlf()
+
+ Dialog.useAutoOpen(control, 3e3)
+
+ const onClose = React.useCallback(() => {
+ nuxDialogs.dismissActiveNux()
+ }, [nuxDialogs])
+
+ const onChangeFontFamily = React.useCallback(
+ (values: string[]) => {
+ const next = values[0] === 'system' ? 'system' : 'theme'
+ fonts.setFontFamily(next)
+ },
+ [fonts],
+ )
+
+ const onChangeFontScale = React.useCallback(
+ (values: string[]) => {
+ const next = values[0] || ('0' as any)
+ fonts.setFontScale(next)
+ },
+ [fonts],
+ )
+
+ return (
+
+
+
+
+
+
+
+ Introducing new font settings ✨
+
+
+
+ To the ensure the best possible experience, we're introducing a
+ new theme font, along with adjustable font sizing settings.
+
+
+
+
+ Defaults are shown below. You can edit these in your Appearance
+ Settings later.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index a38c87b68e..b93831ad35 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -1,4 +1,5 @@
import React from 'react'
+import {AppBskyActorDefs} from '@atproto/api'
import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
@@ -8,9 +9,16 @@ import {
useRemoveNuxsMutation,
useUpsertNuxMutation,
} from '#/state/queries/nuxs'
-import {useSession} from '#/state/session'
+import {
+ usePreferencesQuery,
+ UsePreferencesQueryResponse,
+} from '#/state/queries/preferences'
+import {useProfileQuery} from '#/state/queries/profile'
+import {SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell'
+import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography'
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
+// NUXs
import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
import {IS_DEV} from '#/env'
@@ -21,11 +29,27 @@ type Context = {
const queuedNuxs: {
id: Nux
- enabled?: (props: {gate: ReturnType}) => boolean
+ enabled?: (props: {
+ gate: ReturnType
+ currentAccount: SessionAccount
+ currentProfile: AppBskyActorDefs.ProfileViewDetailed
+ preferences: UsePreferencesQueryResponse
+ }) => boolean
}[] = [
{
id: Nux.TenMillionDialog,
},
+ {
+ id: Nux.NeueTypography,
+ enabled(props) {
+ if (props.currentProfile.createdAt) {
+ if (new Date(props.currentProfile.createdAt) < new Date('2024-09-25')) {
+ return true
+ }
+ }
+ return false
+ },
+ },
]
const Context = React.createContext({
@@ -38,12 +62,31 @@ export function useNuxDialogContext() {
}
export function NuxDialogs() {
- const {hasSession} = useSession()
- const onboardingState = useOnboardingState()
- return hasSession && !onboardingState.isActive ? : null
+ const {currentAccount} = useSession()
+ const {data: preferences} = usePreferencesQuery()
+ const {data: profile} = useProfileQuery({did: currentAccount?.did})
+ const onboardingActive = useOnboardingState().isActive
+
+ const isLoading =
+ !currentAccount || !preferences || !profile || onboardingActive
+ return !isLoading ? (
+
+ ) : null
}
-function Inner() {
+function Inner({
+ currentAccount,
+ currentProfile,
+ preferences,
+}: {
+ currentAccount: SessionAccount
+ currentProfile: AppBskyActorDefs.ProfileViewDetailed
+ preferences: UsePreferencesQueryResponse
+}) {
const gate = useGate()
const {nuxs} = useNuxs()
const [snoozed, setSnoozed] = React.useState(() => {
@@ -80,10 +123,19 @@ function Inner() {
const nux = nuxs.find(nux => nux.id === id)
// check if completed first
- if (nux && nux.completed) continue
+ if (nux && nux.completed) {
+ continue
+ }
// then check gate (track exposure)
- if (enabled && !enabled({gate})) continue
+ if (
+ enabled &&
+ !enabled({gate, currentAccount, currentProfile, preferences})
+ ) {
+ continue
+ }
+
+ logger.debug(`NUX dialogs: activating '${id}' NUX`)
// we have a winner
setActiveNux(id)
@@ -104,7 +156,16 @@ function Inner() {
break
}
- }, [nuxs, snoozed, snoozeNuxDialog, upsertNux, gate])
+ }, [
+ nuxs,
+ snoozed,
+ snoozeNuxDialog,
+ upsertNux,
+ gate,
+ currentAccount,
+ currentProfile,
+ preferences,
+ ])
const ctx = React.useMemo(() => {
return {
@@ -116,6 +177,7 @@ function Inner() {
return (
{activeNux === Nux.TenMillionDialog && }
+ {activeNux === Nux.NeueTypography && }
)
}
diff --git a/src/components/icons/TextSize.tsx b/src/components/icons/TextSize.tsx
new file mode 100644
index 0000000000..73a6a085db
--- /dev/null
+++ b/src/components/icons/TextSize.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const TextSize_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M9 5a1 1 0 0 1 1-1h12a1 1 0 1 1 0 2h-5v14a1 1 0 1 1-2 0V6h-5a1 1 0 0 1-1-1Zm-3.073 7v8a1 1 0 1 0 2 0v-8H12a1 1 0 1 0 0-2H6.971a1.015 1.015 0 0 0-.089 0H2a1 1 0 1 0 0 2h3.927Z',
+})
diff --git a/src/components/icons/TitleCase.tsx b/src/components/icons/TitleCase.tsx
new file mode 100644
index 0000000000..9d040c9e5a
--- /dev/null
+++ b/src/components/icons/TitleCase.tsx
@@ -0,0 +1,5 @@
+import {createSinglePathSVG} from './TEMPLATE'
+
+export const TitleCase_Stroke2_Corner0_Rounded = createSinglePathSVG({
+ path: 'M3.65 17.247c-.242.832-.632 1.178-1.325 1.178-.814 0-1.325-.476-1.325-1.23 0-.216.06-.51.173-.831L4.586 7.07c.364-1.014.979-1.482 1.966-1.482 1.022 0 1.629.45 2.001 1.473l3.43 9.303c.121.337.165.571.165.831 0 .72-.546 1.23-1.308 1.23-.736 0-1.126-.338-1.36-1.152l-.658-1.975H4.309l-.658 1.95ZM6.5 8.152l-1.62 5.12h3.335l-1.654-5.12H6.5Zm13.005 8.688c-.52.988-1.68 1.568-2.84 1.568-1.768 0-3.11-1.144-3.11-2.815 0-1.69 1.299-2.668 3.62-2.807l2.34-.138v-.615c0-.867-.607-1.369-1.56-1.369-.771 0-1.239.251-1.802.979-.277.312-.597.468-1.004.468-.615 0-1.057-.399-1.057-.97 0-.2.043-.382.13-.572.433-1.109 1.923-1.793 3.845-1.793 2.383 0 3.933 1.23 3.933 3.1v5.293c0 .84-.511 1.273-1.23 1.273-.684 0-1.16-.38-1.213-1.126v-.476h-.052Zm-3.43-1.386c0 .693.572 1.126 1.42 1.126 1.11 0 2.02-.719 2.02-1.723v-.676l-1.959.121c-.944.07-1.48.494-1.48 1.152Z',
+})
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
index d0ea4cdc17..6a3d796110 100644
--- a/src/lib/styles.ts
+++ b/src/lib/styles.ts
@@ -79,13 +79,13 @@ export const s = StyleSheet.create({
// font weights
fw600: {fontWeight: '600'},
- bold: {fontWeight: 'bold'},
+ bold: {fontWeight: '700'},
fw500: {fontWeight: '500'},
semiBold: {fontWeight: '500'},
fw400: {fontWeight: '400'},
normal: {fontWeight: '400'},
- fw300: {fontWeight: '300'},
- light: {fontWeight: '300'},
+ fw300: {fontWeight: '400'},
+ light: {fontWeight: '400'},
fw200: {fontWeight: '200'},
// text decoration
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
index 9590f16596..d16f9f632a 100644
--- a/src/lib/themes.ts
+++ b/src/lib/themes.ts
@@ -1,5 +1,6 @@
import {Platform} from 'react-native'
+import {tokens} from '#/alf'
import {darkPalette, dimPalette, lightPalette} from '#/alf/themes'
import {colors} from './styles'
import type {Theme} from './ThemeContext'
@@ -88,163 +89,163 @@ export const defaultTheme: Theme = {
typography: {
'2xl-thin': {
fontSize: 18,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
'2xl': {
fontSize: 18,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'2xl-medium': {
fontSize: 18,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'2xl-bold': {
fontSize: 18,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'2xl-heavy': {
fontSize: 18,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'xl-thin': {
fontSize: 17,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
xl: {
fontSize: 17,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'xl-medium': {
fontSize: 17,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'xl-bold': {
fontSize: 17,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'xl-heavy': {
fontSize: 17,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'lg-thin': {
fontSize: 16,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
lg: {
fontSize: 16,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'lg-medium': {
fontSize: 16,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'lg-bold': {
fontSize: 16,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'lg-heavy': {
fontSize: 16,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'md-thin': {
fontSize: 15,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
md: {
fontSize: 15,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'md-medium': {
fontSize: 15,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'md-bold': {
fontSize: 15,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'md-heavy': {
fontSize: 15,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'sm-thin': {
fontSize: 14,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
sm: {
fontSize: 14,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'sm-medium': {
fontSize: 14,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'sm-bold': {
fontSize: 14,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'sm-heavy': {
fontSize: 14,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'xs-thin': {
fontSize: 13,
- letterSpacing: 0.25,
- fontWeight: '300',
+ letterSpacing: tokens.TRACKING,
+ fontWeight: '400',
},
xs: {
fontSize: 13,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'xs-medium': {
fontSize: 13,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'xs-bold': {
fontSize: 13,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '700',
},
'xs-heavy': {
fontSize: 13,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '800',
},
'title-2xl': {
fontSize: 34,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'title-xl': {
fontSize: 28,
- letterSpacing: 0.25,
+ letterSpacing: tokens.TRACKING,
fontWeight: '500',
},
'title-lg': {
@@ -254,32 +255,32 @@ export const defaultTheme: Theme = {
title: {
fontWeight: '500',
fontSize: 20,
- letterSpacing: 0.15,
+ letterSpacing: tokens.TRACKING,
},
'title-sm': {
fontWeight: 'bold',
fontSize: 17,
- letterSpacing: 0.15,
+ letterSpacing: tokens.TRACKING,
},
'post-text': {
fontSize: 16,
- letterSpacing: 0.2,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'post-text-lg': {
fontSize: 20,
- letterSpacing: 0.2,
+ letterSpacing: tokens.TRACKING,
fontWeight: '400',
},
'button-lg': {
fontWeight: '500',
fontSize: 18,
- letterSpacing: 0.5,
+ letterSpacing: tokens.TRACKING,
},
button: {
fontWeight: '500',
fontSize: 14,
- letterSpacing: 0.5,
+ letterSpacing: tokens.TRACKING,
},
mono: {
fontSize: 14,
diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx
index 00a04bbfb6..d675fb38ed 100644
--- a/src/screens/Settings/AppearanceSettings.tsx
+++ b/src/screens/Settings/AppearanceSettings.tsx
@@ -14,17 +14,21 @@ import {s} from '#/lib/styles'
import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
import {ScrollView} from '#/view/com/util/Views'
-import {atoms as a, native, useTheme} from '#/alf'
+import {atoms as a, native, useAlf, useTheme} from '#/alf'
import * as ToggleButton from '#/components/forms/ToggleButton'
+import {Props as SVGIconProps} from '#/components/icons/common'
import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
+import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
+import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
import {Text} from '#/components/Typography'
type Props = NativeStackScreenProps
export function AppearanceSettingsScreen({}: Props) {
- const {_} = useLingui()
const t = useTheme()
+ const {_} = useLingui()
const {isTabletOrMobile} = useWebMediaQueries()
+ const {fonts} = useAlf()
const {colorMode, darkTheme} = useThemePrefs()
const {setColorMode, setDarkTheme} = useSetThemePrefs()
@@ -54,6 +58,22 @@ export function AppearanceSettingsScreen({}: Props) {
[setDarkTheme, darkTheme],
)
+ const onChangeFontFamily = useCallback(
+ (values: string[]) => {
+ const next = values[0] === 'system' ? 'system' : 'theme'
+ fonts.setFontFamily(next)
+ },
+ [fonts],
+ )
+
+ const onChangeFontScale = useCallback(
+ (values: string[]) => {
+ const next = values[0] || ('0' as any)
+ fonts.setFontScale(next)
+ },
+ [fonts],
+ )
+
return (
@@ -71,65 +91,143 @@ export function AppearanceSettingsScreen({}: Props) {
-
-
-
-
- Mode
-
-
-
-
-
- System
-
-
-
-
- Light
-
-
-
-
- Dark
-
-
-
- {colorMode !== 'light' && (
-
-
-
-
- Dark theme
-
-
+
+
+
-
-
-
- Dim
-
-
-
-
- Dark
-
-
-
-
- )}
+ {colorMode !== 'light' && (
+
+
+
+ )}
+
+
+
+
+
)
}
+
+export function AppearanceToggleButtonGroup({
+ title,
+ description,
+ icon: Icon,
+ items,
+ values,
+ onChange,
+}: {
+ title: string
+ description?: string
+ icon: React.ComponentType
+ items: {
+ label: string
+ name: string
+ }[]
+ values: string[]
+ onChange: (values: string[]) => void
+}) {
+ const t = useTheme()
+ return (
+
+
+
+
+ {title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ {items.map(item => (
+
+ {item.label}
+
+ ))}
+
+
+ )
+}
diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts
index 865967d37a..63a8079623 100644
--- a/src/state/queries/nuxs/definitions.ts
+++ b/src/state/queries/nuxs/definitions.ts
@@ -4,15 +4,22 @@ import {BaseNux} from '#/state/queries/nuxs/types'
export enum Nux {
TenMillionDialog = 'TenMillionDialog',
+ NeueTypography = 'NeueTypography',
}
export const nuxNames = new Set(Object.values(Nux))
-export type AppNux = BaseNux<{
- id: Nux.TenMillionDialog
- data: undefined
-}>
+export type AppNux =
+ | BaseNux<{
+ id: Nux.TenMillionDialog
+ data: undefined
+ }>
+ | BaseNux<{
+ id: Nux.NeueTypography
+ data: undefined
+ }>
export const NuxSchemas: Record | undefined> = {
[Nux.TenMillionDialog]: undefined,
+ [Nux.NeueTypography]: undefined,
}
diff --git a/src/storage/index.ts b/src/storage/index.ts
index 819ffab7ec..4be08170dd 100644
--- a/src/storage/index.ts
+++ b/src/storage/index.ts
@@ -2,6 +2,8 @@ import {MMKV} from 'react-native-mmkv'
import {Device} from '#/storage/schema'
+export * from '#/storage/schema'
+
/**
* Generic storage class. DO NOT use this directly. Instead, use the exported
* storage instances below.
diff --git a/src/storage/schema.ts b/src/storage/schema.ts
index bc41fd3edf..1a9656fede 100644
--- a/src/storage/schema.ts
+++ b/src/storage/schema.ts
@@ -2,5 +2,7 @@
* Device data that's specific to the device and does not vary based account
*/
export type Device = {
+ fontScale: '-2' | '-1' | '0' | '1' | '2'
+ fontFamily: 'system' | 'theme'
lastNuxDialog: string | undefined
}
diff --git a/src/view/com/util/text/Text.tsx b/src/view/com/util/text/Text.tsx
index 2ea9586eed..52a45b0e2e 100644
--- a/src/view/com/util/text/Text.tsx
+++ b/src/view/com/util/text/Text.tsx
@@ -1,10 +1,11 @@
import React from 'react'
-import {Text as RNText, TextProps} from 'react-native'
+import {StyleSheet, Text as RNText, TextProps} from 'react-native'
import {UITextView} from 'react-native-uitextview'
import {lh, s} from 'lib/styles'
import {TypographyVariant, useTheme} from 'lib/ThemeContext'
import {isIOS, isWeb} from 'platform/detection'
+import {applyFonts, useAlf} from '#/alf'
export type CustomTextProps = TextProps & {
type?: TypographyVariant
@@ -32,11 +33,28 @@ export function Text({
const theme = useTheme()
const typography = theme.typography[type]
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
+ const {fonts} = useAlf()
if (selectable && isIOS) {
+ const flattened = StyleSheet.flatten([
+ s.black,
+ typography,
+ lineHeightStyle,
+ style,
+ ])
+
+ applyFonts(flattened, fonts.family)
+
+ // should always be defined on `typography`
+ // @ts-ignore
+ if (flattened.fontSize) {
+ // @ts-ignore
+ flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
+ }
+
return (
@@ -45,15 +63,26 @@ export function Text({
)
}
+ const flattened = StyleSheet.flatten([
+ s.black,
+ typography,
+ isWeb && fontFamilyStyle,
+ lineHeightStyle,
+ style,
+ ])
+
+ applyFonts(flattened, fonts.family)
+
+ // should always be defined on `typography`
+ // @ts-ignore
+ if (flattened.fontSize) {
+ // @ts-ignore
+ flattened.fontSize = flattened.fontSize * fonts.scaleMultiplier
+ }
+
return (