diff --git a/.eslintrc.js b/.eslintrc.js index 2d5f2822ac..f6407fa6fa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -33,6 +33,7 @@ module.exports = { ], 'bsky-internal/use-exact-imports': 'error', 'bsky-internal/use-typed-gates': 'error', + 'bsky-internal/use-prefixed-imports': 'warn', 'simple-import-sort/imports': [ 'warn', { diff --git a/.github/workflows/golang-test-lint.yml b/.github/workflows/golang-test-lint.yml index 36e28841d5..c8124dbade 100644 --- a/.github/workflows/golang-test-lint.yml +++ b/.github/workflows/golang-test-lint.yml @@ -21,7 +21,7 @@ jobs: with: go-version: '1.22' - name: Dummy Static Files - run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt + run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt - name: Check run: cd bskyweb/ && make check - name: Build (binary) @@ -38,6 +38,6 @@ jobs: with: go-version: '1.22' - name: Dummy Static Files - run: touch bskyweb/static/js/blah.js && touch bskyweb/static/media/blah.txt + run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt - name: Lint run: cd bskyweb/ && make lint diff --git a/.prettierignore b/.prettierignore index 641a1b8bfc..8ccbae2148 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,4 @@ -# Ignore everything except JS-ey code. +# Ignore everything except JS-ey or CSS code. # Based on https://stackoverflow.com/a/70715829/458193 * !**/*.js @@ -7,6 +7,8 @@ !**/*.tsx !*/ +!**/*.css + # More specific ignores go below. .expo android diff --git a/Dockerfile.embedr b/Dockerfile.embedr index 9ff04aa5c7..663cbcfc51 100644 --- a/Dockerfile.embedr +++ b/Dockerfile.embedr @@ -40,6 +40,7 @@ RUN find ./bskyweb/embedr-static && find ./bskyweb/embedr-templates && find ./bs # hack around issue with empty directory and go:embed RUN touch bskyweb/static/js/empty.txt +RUN touch bskyweb/static/css/empty.txt RUN touch bskyweb/static/media/empty.txt # diff --git a/app.config.js b/app.config.js index a55992eb5c..d8da75cb0e 100644 --- a/app.config.js +++ b/app.config.js @@ -55,6 +55,7 @@ module.exports = function (config) { : undefined const UPDATES_ENABLED = !!UPDATES_CHANNEL + const USE_SENTRY = Boolean(process.env.SENTRY_AUTH_TOKEN) const SENTRY_DIST = `${PLATFORM}.${VERSION}.${IS_TESTFLIGHT ? 'tf' : ''}${ IS_DEV ? 'dev' : '' }` @@ -186,7 +187,15 @@ module.exports = function (config) { }, plugins: [ 'expo-localization', - Boolean(process.env.SENTRY_AUTH_TOKEN) && 'sentry-expo', + USE_SENTRY && [ + '@sentry/react-native/expo', + { + organization: 'blueskyweb', + project: 'react-native', + release: VERSION, + dist: SENTRY_DIST, + }, + ], [ 'expo-build-properties', { @@ -221,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: { @@ -263,7 +297,7 @@ module.exports = function (config) { * @see https://docs.expo.dev/guides/using-sentry/#app-configuration */ { - file: 'sentry-expo/upload-sourcemaps', + file: './postHooks/uploadSentrySourcemapsPostHook', config: { organization: 'blueskyweb', project: 'react-native', 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/aspectRatio11_stroke2_corner0_rounded.svg b/assets/icons/aspectRatio11_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..949eba697b --- /dev/null +++ b/assets/icons/aspectRatio11_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/aspectRatio34_stroke2_corner0_rounded.svg b/assets/icons/aspectRatio34_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..50761a05c5 --- /dev/null +++ b/assets/icons/aspectRatio34_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/aspectRatio43_stroke2_corner0_rounded.svg b/assets/icons/aspectRatio43_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..4ca8362e94 --- /dev/null +++ b/assets/icons/aspectRatio43_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/flipHorizontal_stroke2_corner0_rounded.svg b/assets/icons/flipHorizontal_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..0d4c81d2c1 --- /dev/null +++ b/assets/icons/flipHorizontal_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/flipVertical_stroke2_corner0_rounded.svg b/assets/icons/flipVertical_stroke2_corner0_rounded.svg new file mode 100644 index 0000000000..42fca985c5 --- /dev/null +++ b/assets/icons/flipVertical_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + 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/bskyembed/package.json b/bskyembed/package.json index e269b1c953..e3ac528106 100644 --- a/bskyembed/package.json +++ b/bskyembed/package.json @@ -21,7 +21,7 @@ "eslint-config-preact": "^1.3.0", "eslint-plugin-simple-import-sort": "^12.0.0", "postcss": "^8.4.38", - "tailwindcss": "^3.4.3", + "tflwindcss": "^3.4.3", "typescript": "^5.5.4", "vite": "^5.2.8", "vite-tsconfig-paths": "^4.3.2" diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 1ed107b592..82c3fd60a0 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -158,6 +158,12 @@ export function Embed({ return The quoted post is blocked. } + // Case 3.8: Detached quote post + if (AppBskyEmbedRecord.isViewDetached(record)) { + // Just don't show anything + return null + } + // Unknown embed type return null } diff --git a/bskyembed/src/index.css b/bskyembed/src/index.css index 23457ec28d..22b2b8be5c 100644 --- a/bskyembed/src/index.css +++ b/bskyembed/src/index.css @@ -4,4 +4,4 @@ .break-word { word-break: break-word; -} \ No newline at end of file +} diff --git a/bskyweb/.gitignore b/bskyweb/.gitignore index 05b3ad7ab5..a63a381f94 100644 --- a/bskyweb/.gitignore +++ b/bskyweb/.gitignore @@ -10,6 +10,10 @@ static/js/*.js static/js/*.map static/js/*.js.LICENSE.txt static/js/empty.txt +static/css/*.css +static/css/*.map +static/css/*.css.LICENSE.txt +static/css/empty.txt static/media/*.png static/media/empty.txt templates/scripts.html diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 2d75a2b723..fd80a5ed14 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -210,6 +210,11 @@ func serve(cctx *cli.Context) error { maxAge = 7 * (60 * 60 * 24) // 1 week } + // fonts can be cached for a year + if strings.HasSuffix(path, ".otf") { + maxAge = 365 * (60 * 60 * 24) // 1 year + } + c.Response().Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge)) return next(c) } diff --git a/bskyweb/static/css/.gitkeep b/bskyweb/static/css/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bskyweb/static/media/Inter-Black.66e9a87f1c921e844ed4.otf b/bskyweb/static/media/Inter-Black.66e9a87f1c921e844ed4.otf new file mode 100644 index 0000000000..44d1779af6 Binary files /dev/null and b/bskyweb/static/media/Inter-Black.66e9a87f1c921e844ed4.otf differ diff --git a/bskyweb/static/media/Inter-BlackItalic.27b9f0ad06fd13a7b9da.otf b/bskyweb/static/media/Inter-BlackItalic.27b9f0ad06fd13a7b9da.otf new file mode 100644 index 0000000000..6fc475e415 Binary files /dev/null and b/bskyweb/static/media/Inter-BlackItalic.27b9f0ad06fd13a7b9da.otf differ diff --git a/bskyweb/static/media/Inter-Bold.8d330503e1d034ad68de.otf b/bskyweb/static/media/Inter-Bold.8d330503e1d034ad68de.otf new file mode 100644 index 0000000000..58a38073e8 Binary files /dev/null and b/bskyweb/static/media/Inter-Bold.8d330503e1d034ad68de.otf differ diff --git a/bskyweb/static/media/Inter-BoldItalic.bb17e63f9baa0d861a20.otf b/bskyweb/static/media/Inter-BoldItalic.bb17e63f9baa0d861a20.otf new file mode 100644 index 0000000000..e67935aa5a Binary files /dev/null and b/bskyweb/static/media/Inter-BoldItalic.bb17e63f9baa0d861a20.otf differ diff --git a/bskyweb/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf b/bskyweb/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf new file mode 100644 index 0000000000..66cd95228c Binary files /dev/null and b/bskyweb/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf differ diff --git a/bskyweb/static/media/Inter-ExtraBoldItalic.0e50b40728d24d40fdf4.otf b/bskyweb/static/media/Inter-ExtraBoldItalic.0e50b40728d24d40fdf4.otf new file mode 100644 index 0000000000..f269814a64 Binary files /dev/null and b/bskyweb/static/media/Inter-ExtraBoldItalic.0e50b40728d24d40fdf4.otf differ diff --git a/bskyweb/static/media/Inter-Italic.95778eb0c75dc956257e.otf b/bskyweb/static/media/Inter-Italic.95778eb0c75dc956257e.otf new file mode 100644 index 0000000000..f78848b987 Binary files /dev/null and b/bskyweb/static/media/Inter-Italic.95778eb0c75dc956257e.otf differ diff --git a/bskyweb/static/media/Inter-Medium.296aa2d65964269836b3.otf b/bskyweb/static/media/Inter-Medium.296aa2d65964269836b3.otf new file mode 100644 index 0000000000..f44f89adac Binary files /dev/null and b/bskyweb/static/media/Inter-Medium.296aa2d65964269836b3.otf differ diff --git a/bskyweb/static/media/Inter-MediumItalic.0e57e17a6311368e2114.otf b/bskyweb/static/media/Inter-MediumItalic.0e57e17a6311368e2114.otf new file mode 100644 index 0000000000..1970f57214 Binary files /dev/null and b/bskyweb/static/media/Inter-MediumItalic.0e57e17a6311368e2114.otf differ diff --git a/bskyweb/static/media/Inter-Regular.1f5ed03b6dd9fd1f9982.otf b/bskyweb/static/media/Inter-Regular.1f5ed03b6dd9fd1f9982.otf new file mode 100644 index 0000000000..2d0bd1d645 Binary files /dev/null and b/bskyweb/static/media/Inter-Regular.1f5ed03b6dd9fd1f9982.otf differ diff --git a/bskyweb/static/media/Inter-SemiBold.2277990330981b8409bb.otf b/bskyweb/static/media/Inter-SemiBold.2277990330981b8409bb.otf new file mode 100644 index 0000000000..52c84550ba Binary files /dev/null and b/bskyweb/static/media/Inter-SemiBold.2277990330981b8409bb.otf differ diff --git a/bskyweb/static/media/Inter-SemiBoldItalic.f62fea3df3a521d6c8a7.otf b/bskyweb/static/media/Inter-SemiBoldItalic.f62fea3df3a521d6c8a7.otf new file mode 100644 index 0000000000..b725bfc883 Binary files /dev/null and b/bskyweb/static/media/Inter-SemiBoldItalic.f62fea3df3a521d6c8a7.otf differ diff --git a/bskyweb/static/media/MaterialIcons.f20305dee9d396fea5c7.ttf b/bskyweb/static/media/MaterialIcons.f20305dee9d396fea5c7.ttf new file mode 100644 index 0000000000..9d09b0feb8 Binary files /dev/null and b/bskyweb/static/media/MaterialIcons.f20305dee9d396fea5c7.ttf differ diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index aa7efc5ebf..eaa31aa4a3 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -13,252 +13,25 @@ - {% include "scripts.html" %} diff --git a/eslint/index.js b/eslint/index.js index cf5d41225d..6f75f1bc34 100644 --- a/eslint/index.js +++ b/eslint/index.js @@ -5,5 +5,6 @@ module.exports = { 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), 'use-exact-imports': require('./use-exact-imports'), 'use-typed-gates': require('./use-typed-gates'), + 'use-prefixed-imports': require('./use-prefixed-imports'), }, } diff --git a/eslint/use-exact-imports.js b/eslint/use-exact-imports.js index 06723043fe..26e688563e 100644 --- a/eslint/use-exact-imports.js +++ b/eslint/use-exact-imports.js @@ -1,4 +1,3 @@ -/* eslint-disable bsky-internal/use-exact-imports */ const BANNED_IMPORTS = [ '@fortawesome/free-regular-svg-icons', '@fortawesome/free-solid-svg-icons', @@ -6,11 +5,12 @@ const BANNED_IMPORTS = [ exports.create = function create(context) { return { - Literal(node) { - if (typeof node.value !== 'string') { + ImportDeclaration(node) { + const source = node.source + if (typeof source.value !== 'string') { return } - if (BANNED_IMPORTS.includes(node.value)) { + if (BANNED_IMPORTS.includes(source.value)) { context.report({ node, message: diff --git a/eslint/use-prefixed-imports.js b/eslint/use-prefixed-imports.js new file mode 100644 index 0000000000..141d536484 --- /dev/null +++ b/eslint/use-prefixed-imports.js @@ -0,0 +1,39 @@ +const BANNED_IMPORT_PREFIXES = [ + 'alf/', + 'components/', + 'lib/', + 'locale/', + 'logger/', + 'platform/', + 'state/', + 'storage/', + 'view/', +] + +module.exports = { + meta: { + type: 'suggestion', + fixable: 'code', + }, + create(context) { + return { + ImportDeclaration(node) { + const source = node.source + if (typeof source.value !== 'string') { + return + } + if ( + BANNED_IMPORT_PREFIXES.some(banned => source.value.startsWith(banned)) + ) { + context.report({ + node: source, + message: `Use '#/${source.value}'`, + fix(fixer) { + return fixer.replaceText(source, `'#/${source.value}'`) + }, + }) + } + }, + } + }, +} diff --git a/package.json b/package.json index 4a6d329f74..5f5fc64479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bsky.app", - "version": "1.91.1", + "version": "1.91.2", "private": true, "engines": { "node": ">=18" @@ -15,7 +15,7 @@ "web": "expo start --web", "use-build-number": "./scripts/useBuildNumberEnv.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/ && cp -v ./web-build/static/media/*.png ./bskyweb/static/media/", + "build-web": "expo export:web && node ./scripts/post-web-build.js", "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-android": "yarn use-build-number-with-bump eas build -p android", @@ -67,7 +67,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-native-fontawesome": "^0.3.2", - "@haileyok/bluesky-video": "0.1.8", + "@haileyok/bluesky-video": "0.1.10", "@lingui/react": "^4.5.0", "@mattermost/react-native-paste-input": "^0.8.0", "@miblanchard/react-native-slider": "^2.3.1", @@ -84,7 +84,7 @@ "@segment/analytics-react": "^1.0.0-rc1", "@segment/analytics-react-native": "^2.10.1", "@segment/sovran-react-native": "^0.4.5", - "@sentry/react-native": "5.5.0", + "@sentry/react-native": "5.32.0", "@tamagui/focus-scope": "^1.84.1", "@tanstack/query-async-storage-persister": "^5.25.0", "@tanstack/react-query": "^5.8.1", @@ -109,6 +109,7 @@ "await-lock": "^2.2.2", "babel-plugin-transform-remove-console": "^6.9.4", "base64-js": "^1.5.1", + "bcp-47": "^2.1.0", "bcp-47-match": "^2.0.3", "date-fns": "^2.30.0", "deprecated-react-native-prop-types": "^5.0.0", @@ -124,6 +125,7 @@ "expo-dev-client": "^5.0.0-canary-20240912-1059f85", "expo-device": "~7.0.0-canary-20240912-1059f85", "expo-file-system": "^18.0.0-canary-20240912-1059f85", + "expo-font": "13.0.0-canary-20240912-1059f85", "expo-haptics": "14.0.0-canary-20240912-1059f85", "expo-image": "~2.0.0-canary-20240912-1059f85", "expo-image-manipulator": "^13.0.0-canary-20240912-1059f85", @@ -200,10 +202,10 @@ "react-responsive": "^9.0.2", "react-textarea-autosize": "^8.5.3", "rn-fetch-blob": "^0.12.0", - "sentry-expo": "~7.0.1", "statsig-react-native-expo": "^4.6.1", "tippy.js": "^6.3.7", "tlds": "^1.234.0", + "tldts": "^6.1.46", "zeego": "^1.6.2", "zod": "^3.20.2" }, diff --git a/patches/@sentry+react-native+5.32.0.patch b/patches/@sentry+react-native+5.32.0.patch new file mode 100644 index 0000000000..a5ccecc21a --- /dev/null +++ b/patches/@sentry+react-native+5.32.0.patch @@ -0,0 +1,36 @@ +diff --git a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js +index 7e0b4cd..177454c 100644 +--- a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js ++++ b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js +@@ -3,6 +3,8 @@ import { LogBox } from 'react-native'; + * This is a workaround for using fetch on RN, this is a known issue in react-native and only generates a warning. + */ + export function ignoreRequireCycleLogs() { +- LogBox.ignoreLogs(['Require cycle:']); ++ try { ++ LogBox.ignoreLogs(['Require cycle:']); ++ } catch (e) {} + } + //# sourceMappingURL=ignorerequirecyclelogs.js.map +\ No newline at end of file +diff --git a/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js b/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js +index 0f244f2..ae7dfb3 100755 +--- a/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js ++++ b/node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps.js +@@ -174,6 +174,7 @@ if (!outputDir) { + process.exit(1); + } + ++const otherArgs = process.argv.slice(3); + const files = getAssetPathsSync(outputDir); + const groupedAssets = groupAssets(files); + +@@ -195,7 +196,7 @@ for (const [assetGroupName, assets] of Object.entries(groupedAssets)) { + + const isHermes = assets.find(asset => asset.endsWith('.hbc')); + const windowsCallback = process.platform === "win32" ? 'node ' : ''; +- execSync(`${windowsCallback}${sentryCliBin} sourcemaps upload ${isHermes ? '--debug-id-reference' : ''} ${assets.join(' ')}`, { ++ execSync(`${windowsCallback}${sentryCliBin} sourcemaps upload ${isHermes ? '--debug-id-reference' : ''} ${assets.join(' ')} ${otherArgs.join(' ')}`, { + env: { + ...process.env, + [SENTRY_PROJECT]: sentryProject, diff --git a/patches/@sentry+react-native+5.5.0.patch b/patches/@sentry+react-native+5.5.0.patch deleted file mode 100644 index 2962aa44c2..0000000000 --- a/patches/@sentry+react-native+5.5.0.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js -index 7e0b4cd..177454c 100644 ---- a/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js -+++ b/node_modules/@sentry/react-native/dist/js/utils/ignorerequirecyclelogs.js -@@ -3,6 +3,8 @@ import { LogBox } from 'react-native'; - * This is a workaround for using fetch on RN, this is a known issue in react-native and only generates a warning. - */ - export function ignoreRequireCycleLogs() { -- LogBox.ignoreLogs(['Require cycle:']); -+ try { -+ LogBox.ignoreLogs(['Require cycle:']); -+ } catch (e) {} - } - //# sourceMappingURL=ignorerequirecyclelogs.js.map -\ No newline at end of file diff --git a/postHooks/uploadSentrySourcemapsPostHook.js b/postHooks/uploadSentrySourcemapsPostHook.js new file mode 100644 index 0000000000..756e64253c --- /dev/null +++ b/postHooks/uploadSentrySourcemapsPostHook.js @@ -0,0 +1,34 @@ +const exec = require('child_process').execSync + +const SENTRY_AUTH_TOKEN = process.env.SENTRY_AUTH_TOKEN + +module.exports = ({config}) => { + if (!SENTRY_AUTH_TOKEN) { + console.log( + 'SENTRY_AUTH_TOKEN environment variable must be set to upload sourcemaps. Skipping.', + ) + return + } + + const org = config.organization + const project = config.project + const release = config.release + const dist = config.dist + + if (!org || !project || !release || !dist) { + console.log( + '"organization", "project", "release", and "dist" must be set in the hook config to upload sourcemaps. Skipping.', + ) + return + } + + try { + console.log('Uploading sourcemaps to Sentry...') + exec( + `node node_modules/@sentry/react-native/scripts/expo-upload-sourcemaps dist --url https://sentry.io/ -o ${org} -p ${project} -r ${release} -d ${dist}`, + ) + console.log('Sourcemaps uploaded to Sentry.') + } catch (e) { + console.error('Error uploading sourcemaps to Sentry:', e) + } +} diff --git a/scripts/post-web-build.js b/scripts/post-web-build.js index baaa7cb8b7..7bbee38554 100644 --- a/scripts/post-web-build.js +++ b/scripts/post-web-build.js @@ -20,7 +20,30 @@ console.log(`Writing ${templateFile}`) const outputFile = entrypoints .map(name => { const file = path.basename(name) - return `` + const ext = path.extname(file) + + if (ext === '.js') { + return `` + } + if (ext === '.css') { + return `` + } + + return '' }) .join('\n') fs.writeFileSync(templateFile, outputFile) + +function copyFiles(sourceDir, targetDir) { + const files = fs.readdirSync(path.join(projectRoot, sourceDir)) + files.forEach(file => { + const sourcePath = path.join(projectRoot, sourceDir, file) + const targetPath = path.join(projectRoot, targetDir, file) + fs.copyFileSync(sourcePath, targetPath) + console.log(`Copied ${sourcePath} to ${targetPath}`) + }) +} + +copyFiles('web-build/static/js', 'bskyweb/static/js') +copyFiles('web-build/static/css', 'bskyweb/static/css') +copyFiles('web-build/static/media', 'bskyweb/static/media') diff --git a/src/App.native.tsx b/src/App.native.tsx index 7c5edb8578..9339e17db3 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -29,6 +29,11 @@ import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {listenSessionDropped} from '#/state/events' +import { + beginResolveGeolocation, + ensureGeolocationResolved, + Provider as GeolocationProvider, +} from '#/state/geolocation' import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' @@ -66,6 +71,11 @@ import {BackgroundNotificationPreferencesProvider} from '../modules/expo-backgro // SplashScreen.preventAutoHideAsync() +/** + * Begin geolocation ASAP + */ +beginResolveGeolocation() + function InnerApp() { const [_isReady, setIsReady] = React.useState(false) const {currentAccount} = useSession() @@ -157,7 +167,9 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { - initPersistedState().then(() => setReady(true)) + Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() => + setReady(true), + ) }, []) if (!isReady) { @@ -169,36 +181,38 @@ function App() { * that is set up in the InnerApp component above. */ return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/App.web.tsx b/src/App.web.tsx index 6efe7cc022..7d98737a3b 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -1,5 +1,6 @@ import 'lib/sentry' // must be near top import 'view/icons' +import './style.css' import React, {useEffect, useState} from 'react' import {KeyboardProvider} from 'react-native-keyboard-controller' @@ -18,6 +19,11 @@ import {Provider as A11yProvider} from '#/state/a11y' import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes' import {Provider as DialogStateProvider} from '#/state/dialogs' import {listenSessionDropped} from '#/state/events' +import { + beginResolveGeolocation, + ensureGeolocationResolved, + Provider as GeolocationProvider, +} from '#/state/geolocation' import {Provider as InvitesStateProvider} from '#/state/invites' import {Provider as LightboxStateProvider} from '#/state/lightbox' import {MessagesProvider} from '#/state/messages' @@ -54,6 +60,11 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo import {Provider as PortalProvider} from '#/components/Portal' import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' +/** + * Begin geolocation ASAP + */ +beginResolveGeolocation() + function InnerApp() { const [isReady, setIsReady] = React.useState(false) const {currentAccount} = useSession() @@ -133,8 +144,8 @@ function InnerApp() { + - @@ -148,7 +159,9 @@ function App() { const [isReady, setReady] = useState(false) React.useEffect(() => { - initPersistedState().then(() => setReady(true)) + Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() => + setReady(true), + ) }, []) if (!isReady) { @@ -160,31 +173,33 @@ function App() { * that is set up in the InnerApp component above. */ return ( - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ) } diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index d2e7ffc2ee..0c8eb330d7 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,22 +273,16 @@ export const atoms = { lineHeight: 1.5, }, tracking_normal: { - letterSpacing: 0, - }, - tracking_wide: { - letterSpacing: 0.25, + letterSpacing: tokens.TRACKING, }, font_normal: { - fontWeight: tokens.fontWeight.normal, - }, - font_semibold: { - fontWeight: tokens.fontWeight.semibold, + fontWeight: tokens.fontWeight.regular, }, font_bold: { - fontWeight: tokens.fontWeight.bold, + fontWeight: tokens.fontWeight.semibold, }, font_heavy: { - fontWeight: tokens.fontWeight.heavy, + fontWeight: tokens.fontWeight.extrabold, }, italic: { fontStyle: 'italic', diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts new file mode 100644 index 0000000000..08cfd9f42d --- /dev/null +++ b/src/alf/fonts.ts @@ -0,0 +1,118 @@ +import {useFonts as defaultUseFonts} from 'expo-font' + +import {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) +} + +/* + * IMPORTANT: This is unused. Expo statically extracts these fonts, but we load + * them manually so that we can parallelize the loading along with the JS + * bundle. + * + * See `#/alf/util/useFonts` for the actually used hooks. + * + * All used fonts MUST be configured here. Unused fonts are commented out, but + * the files are there if we need them. + */ +export function DO_NOT_USE() { + 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 + } + } + + /** + * Disable contextual ligatures + * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant} + */ + style.fontVariant = ['no-contextual'] +} 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..3f30702e85 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)', @@ -43,11 +47,16 @@ export const borderRadius = { full: 999, } as const +/** + * These correspond to Inter font files we actually load. + */ export const fontWeight = { - normal: '400', - semibold: '500', - bold: '600', - heavy: '700', + regular: '400', + // medium: '500', + semibold: '600', + // bold: '700', + extrabold: '800', + // black: '900', } as const export const gradients = { diff --git a/src/components/AppLanguageDropdown.tsx b/src/components/AppLanguageDropdown.tsx index 02cd0ce2d4..6170ab2e20 100644 --- a/src/components/AppLanguageDropdown.tsx +++ b/src/components/AppLanguageDropdown.tsx @@ -24,8 +24,6 @@ export function AppLanguageDropdown() { if (sanitizedLang !== value) { setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) } - setLangPrefs.setPrimaryLanguage(value) - setLangPrefs.setContentLanguage(value) // reset feeds to refetch content resetPostsFeedQueries(queryClient) diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx index a106d99663..00a7b53011 100644 --- a/src/components/AppLanguageDropdown.web.tsx +++ b/src/components/AppLanguageDropdown.web.tsx @@ -27,8 +27,6 @@ export function AppLanguageDropdown() { if (sanitizedLang !== value) { setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) } - setLangPrefs.setPrimaryLanguage(value) - setLangPrefs.setContentLanguage(value) // reset feeds to refetch content resetPostsFeedQueries(queryClient) diff --git a/src/components/Button.tsx b/src/components/Button.tsx index d65444e1f7..8728b88c2c 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -7,7 +7,6 @@ import { PressableProps, StyleProp, StyleSheet, - Text, TextProps, TextStyle, View, @@ -15,9 +14,9 @@ import { } from 'react-native' import {LinearGradient} from 'expo-linear-gradient' -import {android, atoms as a, flatten, select, tokens, useTheme} from '#/alf' +import {atoms as a, flatten, select, tokens, useTheme, web} 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 = @@ -31,7 +30,7 @@ export type ButtonColor = | 'gradient_sunset' | 'gradient_nordic' | 'gradient_bonfire' -export type ButtonSize = 'tiny' | 'xsmall' | 'small' | 'medium' | 'large' +export type ButtonSize = 'tiny' | 'small' | 'large' export type ButtonShape = 'round' | 'square' | 'default' export type VariantProps = { /** @@ -344,39 +343,46 @@ export const Button = React.forwardRef( if (shape === 'default') { if (size === 'large') { - baseStyles.push( - {paddingVertical: 15}, - a.px_2xl, - a.rounded_sm, - a.gap_md, - ) - } else if (size === 'medium') { - baseStyles.push( - {paddingVertical: 12}, - a.px_2xl, - a.rounded_sm, - a.gap_md, - ) + baseStyles.push({ + paddingVertical: 13, + paddingHorizontal: 20, + borderRadius: 8, + gap: 8, + }) } else if (size === 'small') { - baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm) - } else if (size === 'xsmall') { - baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm) + baseStyles.push({ + paddingVertical: 8, + paddingHorizontal: 12, + borderRadius: 6, + gap: 6, + }) } else if (size === 'tiny') { - baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs) + baseStyles.push({ + paddingVertical: 4, + paddingHorizontal: 8, + borderRadius: 4, + gap: 4, + }) } } else if (shape === 'round' || shape === 'square') { if (size === 'large') { if (shape === 'round') { - baseStyles.push({height: 54, width: 54}) + baseStyles.push({height: 46, width: 46}) } else { - baseStyles.push({height: 50, width: 50}) + baseStyles.push({height: 44, width: 44}) } } else if (size === 'small') { - baseStyles.push({height: 34, width: 34}) - } else if (size === 'xsmall') { - baseStyles.push({height: 28, width: 28}) + if (shape === 'round') { + baseStyles.push({height: 36, width: 36}) + } else { + baseStyles.push({height: 34, width: 34}) + } } else if (size === 'tiny') { - baseStyles.push({height: 20, width: 20}) + if (shape === 'round') { + baseStyles.push({height: 22, width: 22}) + } else { + baseStyles.push({height: 21, width: 21}) + } } if (shape === 'round') { @@ -620,11 +626,11 @@ export function useSharedButtonTextStyles() { } if (size === 'large') { - baseStyles.push(a.text_md, android({paddingBottom: 1})) + baseStyles.push(a.text_md, a.leading_tight, web({paddingTop: 1})) + } else if (size === 'small') { + baseStyles.push(a.text_sm, a.leading_tight, web({paddingTop: 1})) } else if (size === 'tiny') { - baseStyles.push(a.text_xs, android({paddingBottom: 1})) - } else { - baseStyles.push(a.text_sm, android({paddingBottom: 1})) + baseStyles.push(a.text_xs, a.leading_tight) } return StyleSheet.flatten(baseStyles) @@ -635,14 +641,7 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { const textStyles = useSharedButtonTextStyles() return ( - + {children} ) @@ -651,31 +650,98 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) { export function ButtonIcon({ icon: Comp, position, - size: iconSize, + size, }: { icon: React.ComponentType position?: 'left' | 'right' size?: SVGIconProps['size'] }) { - const {size, disabled} = useButtonContext() + const {size: buttonSize, disabled} = useButtonContext() const textStyles = useSharedButtonTextStyles() + const {iconSize, iconContainerSize} = React.useMemo(() => { + /** + * Pre-set icon sizes for different button sizes + */ + const iconSizeShorthand = + size ?? + (({ + large: 'sm', + small: 'xs', + tiny: 'xs', + }[buttonSize || 'small'] || 'sm') as Exclude< + SVGIconProps['size'], + undefined + >) + + /* + * Copied here from icons/common.tsx so we can tweak if we need to, but + * also so that we can calculate transforms. + */ + const iconSize = { + xs: 12, + sm: 16, + md: 20, + lg: 24, + xl: 28, + '2xl': 32, + }[iconSizeShorthand] + + /* + * Goal here is to match rendered text size so that different size icons + * don't increase button size + */ + const iconContainerSize = { + large: 18, + small: 16, + tiny: 13, + }[buttonSize || 'small'] + + return { + iconSize, + iconContainerSize, + } + }, [buttonSize, size]) return ( - + + + ) } 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/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 542f2d2993..851645a48c 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -9,6 +9,7 @@ import {sanitizeHandle} from '#/lib/strings/handles' import {useLabelerInfoQuery} from '#/state/queries/labeler' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, ViewStyleProp} from '#/alf' +import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {Link as InternalLink, LinkProps} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' @@ -43,21 +44,40 @@ export function Avatar({avatar}: {avatar?: string}) { } export function Title({value}: {value: string}) { - return {value} + return {value} } export function Description({value, handle}: {value?: string; handle: string}) { return value ? ( - + ) : ( - + By {sanitizeHandle(handle, '@')} ) } +export function RegionalNotice() { + const t = useTheme() + return ( + + + + Required in your region + + + ) +} + export function LikeCount({count}: {count: number}) { const t = useTheme() return ( @@ -66,7 +86,7 @@ export function LikeCount({count}: {count: number}) { a.mt_sm, a.text_sm, t.atoms.text_contrast_medium, - {fontWeight: '500'}, + {fontWeight: '600'}, ]}> @@ -85,7 +105,7 @@ export function Content({children}: React.PropsWithChildren<{}>) { a.align_center, a.justify_between, ]}> - {children} + {children} diff --git a/src/components/MediaInsetBorder.tsx b/src/components/MediaInsetBorder.tsx index ef8b00e2e0..ed89880f40 100644 --- a/src/components/MediaInsetBorder.tsx +++ b/src/components/MediaInsetBorder.tsx @@ -24,7 +24,7 @@ export function MediaInsetBorder({ return ( {children} diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx index 742a11667c..6c8084743f 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -132,7 +132,7 @@ export function Label({ currentAccount?.did === profile.did, [currentAccount, profile], ) + const isLabeler = profile.associated?.labeler return ( @@ -419,11 +420,13 @@ function Inner({ {!isMe && + !isLabeler && (isBlockedUser ? ( }) { diff --git a/src/components/ProgressGuide/Task.tsx b/src/components/ProgressGuide/Task.tsx index a83715a425..f2ceba52ac 100644 --- a/src/components/ProgressGuide/Task.tsx +++ b/src/components/ProgressGuide/Task.tsx @@ -35,9 +35,7 @@ export function ProgressGuideTask({ )} - - {title} - + {title} {subtitle && ( diff --git a/src/components/ProgressGuide/Toast.tsx b/src/components/ProgressGuide/Toast.tsx index 346312af51..69e0082606 100644 --- a/src/components/ProgressGuide/Toast.tsx +++ b/src/components/ProgressGuide/Toast.tsx @@ -154,7 +154,7 @@ export const ProgressGuideToast = React.forwardRef< ref={animatedCheckRef} /> - {title} + {title} {subtitle && ( {subtitle} diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index 86cb5c315a..8765cdee31 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -59,7 +59,9 @@ export function Outer({ export function TitleText({children}: React.PropsWithChildren<{}>) { const {titleId} = React.useContext(Context) return ( - + {children} ) @@ -118,7 +120,7 @@ export function Cancel({ + + {show && !fallback && ( + setShow(false)} + onFallback={onHandleFallback} + /> + )} + + + + + Bluesky is celebrating 10 million users! + + + + + Together, we're rebuilding the social internet. We're glad you're + here. + + + + + To learn more,{' '} + { + control.close() + }} + style={[a.text_md, a.leading_snug]}> + check out our post. + + + + + + + ) +} + +export function Icon({width, style}: {width: number} & ViewStyleProp) { + return ( + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx index 267065672c..21e775a108 100644 --- a/src/components/dialogs/nuxs/TenMillion/index.tsx +++ b/src/components/dialogs/nuxs/TenMillion/index.tsx @@ -19,10 +19,10 @@ import {isIOS, isNative} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useProfileQuery} from '#/state/queries/profile' import {useAgent, useSession} from '#/state/session' -import {useComposerControls} from 'state/shell' +import {useComposerControls} from '#/state/shell' import {formatCount} from '#/view/com/util/numeric/format' +import * as Toast from '#/view/com/util/Toast' import {Logomark} from '#/view/icons/Logomark' -import * as Toast from 'view/com/util/Toast' import { atoms as a, ThemeProvider, @@ -87,7 +87,15 @@ function Frame({children}: {children: React.ReactNode}) { ) } -export function TenMillion() { +export function TenMillion({ + showTimeout, + onClose, + onFallback, +}: { + showTimeout?: number + onClose?: () => void + onFallback?: () => void +}) { const agent = useAgent() const nuxDialogs = useNuxDialogContext() const [userNumber, setUserNumber] = React.useState(0) @@ -120,7 +128,11 @@ export function TenMillion() { } else { // should be rare nuxDialogs.dismissActiveNux() + onFallback?.() } + } else { + nuxDialogs.dismissActiveNux() + onFallback?.() } } @@ -128,6 +140,7 @@ export function TenMillion() { fetching.current = true networkRetry(3, fetchUserNumber).catch(() => { nuxDialogs.dismissActiveNux() + onFallback?.() }) } }, [ @@ -136,12 +149,27 @@ export function TenMillion() { setUserNumber, nuxDialogs.dismissActiveNux, nuxDialogs, + onFallback, ]) - return userNumber ? : null + return userNumber ? ( + + ) : null } -export function TenMillionInner({userNumber}: {userNumber: number}) { +export function TenMillionInner({ + userNumber, + showTimeout, + onClose: onCloseOuter, +}: { + userNumber: number + showTimeout: number + onClose?: () => void +}) { const t = useTheme() const lightTheme = useTheme('light') const {_, i18n} = useLingui() @@ -169,6 +197,27 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { const isLoadingData = isProfileLoading || !moderation || !profile const isLoadingImage = !uri + const displayName = React.useMemo(() => { + if (!profile || !moderation) return '' + return sanitizeDisplayName( + profile.displayName || sanitizeHandle(profile.handle), + moderation.ui('displayName'), + ) + }, [profile, moderation]) + const handle = React.useMemo(() => { + if (!profile) return '' + return sanitizeHandle(profile.handle, '@') + }, [profile]) + const joinedDate = React.useMemo(() => { + if (!profile || !profile.createdAt) return '' + const date = i18n.date(profile.createdAt, { + month: 'short', + day: 'numeric', + year: 'numeric', + }) + return date + }, [i18n, profile]) + const error: string = React.useMemo(() => { if (profileError) { return _( @@ -184,14 +233,15 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { React.useEffect(() => { const timeout = setTimeout(() => { control.open() - }, 3e3) + }, showTimeout) return () => { clearTimeout(timeout) } - }, [control]) + }, [control, showTimeout]) const onClose = React.useCallback(() => { nuxDialogs.dismissActiveNux() - }, [nuxDialogs]) + onCloseOuter?.() + }, [nuxDialogs, onCloseOuter]) /* * Actions @@ -206,19 +256,34 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { msg`Bluesky now has over 10 million users, and I was #${i18n.number( userNumber, )}!`, - ), // TODO + ), imageUris: [ { uri, width: WIDTH, height: HEIGHT, + altText: _( + msg`A virtual certificate with text "Celebrating 10M users on Bluesky, #${i18n.number( + userNumber, + )}, ${displayName} ${handle}, joined on ${joinedDate}"`, + ), }, ], }) }, 1e3) }) } - }, [_, i18n, control, openComposer, uri, userNumber]) + }, [ + _, + i18n, + control, + openComposer, + uri, + userNumber, + displayName, + handle, + joinedDate, + ]) const onNativeShare = React.useCallback(() => { if (uri) { control.close(() => { @@ -376,10 +441,10 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { allowFontScaling={false} style={[ a.absolute, + a.font_heavy, { color: t.palette.primary_500, fontSize: 32, - fontWeight: '900', width: 32, top: isNative ? -10 : 0, left: 0, @@ -397,11 +462,11 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { style={[ a.relative, a.text_center, + a.font_heavy, { fontStyle: 'italic', fontSize: getFontSize(userNumber), lineHeight: getFontSize(userNumber), - fontWeight: '900', letterSpacing: -2, }, ]}> @@ -461,11 +526,7 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { a.leading_tight, {maxWidth: '60%'}, ]}> - {sanitizeDisplayName( - profile.displayName || - sanitizeHandle(profile.handle), - moderation.ui('displayName'), - )} + {displayName} @@ -475,11 +536,11 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { style={[ a.flex_1, a.text_sm, - a.font_semibold, + a.font_bold, a.leading_snug, lightTheme.atoms.text_contrast_medium, ]}> - {sanitizeHandle(profile.handle, '@')} + {handle} {profile.createdAt && ( @@ -490,19 +551,12 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { style={[ a.flex_1, a.text_sm, - a.font_semibold, + a.font_bold, a.leading_snug, a.text_right, lightTheme.atoms.text_contrast_low, ]}> - - Joined{' '} - {i18n.date(profile.createdAt, { - month: 'short', - day: 'numeric', - year: 'numeric', - })} - + Joined on {joinedDate} )} @@ -589,14 +643,7 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { + style={[a.text_5xl, a.leading_tight, a.pb_lg, a.font_heavy]}> Thanks for being one of our first 10 million users. @@ -617,9 +664,12 @@ export function TenMillionInner({userNumber}: {userNumber: number}) { a.gap_md, a.pt_xl, ]}> - - Brag a little! - + {gtMobile && ( + + Brag a little! + + )} diff --git a/src/screens/Login/ForgotPasswordForm.tsx b/src/screens/Login/ForgotPasswordForm.tsx index 8588888b87..7acaae5101 100644 --- a/src/screens/Login/ForgotPasswordForm.tsx +++ b/src/screens/Login/ForgotPasswordForm.tsx @@ -129,7 +129,7 @@ export const ForgotPasswordForm = ({ label={_(msg`Back`)} variant="solid" color="secondary" - size="medium" + size="large" onPress={onPressBack}> Back @@ -143,7 +143,7 @@ export const ForgotPasswordForm = ({ label={_(msg`Next`)} variant="solid" color={'primary'} - size="medium" + size="large" onPress={onPressNext}> Next @@ -170,7 +170,7 @@ export const ForgotPasswordForm = ({ onPress={onEmailSent} label={_(msg`Go to next`)} accessibilityHint={_(msg`Navigates to the next screen`)} - size="medium" + size="large" variant="ghost" color="secondary"> diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index 9a01c04990..c2038b2879 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -285,7 +285,7 @@ export const LoginForm = ({ label={_(msg`Back`)} variant="solid" color="secondary" - size="medium" + size="large" onPress={onPressBack}> Back @@ -299,7 +299,7 @@ export const LoginForm = ({ accessibilityHint={_(msg`Retries login`)} variant="solid" color="secondary" - size="medium" + size="large" onPress={onPressRetryConnect}> Retry @@ -319,7 +319,7 @@ export const LoginForm = ({ accessibilityHint={_(msg`Navigates to the next screen`)} variant="solid" color="primary" - size="medium" + size="large" onPress={onPressNext}> Next diff --git a/src/screens/Login/PasswordUpdatedForm.tsx b/src/screens/Login/PasswordUpdatedForm.tsx index 5407f3f1e3..03e7d86696 100644 --- a/src/screens/Login/PasswordUpdatedForm.tsx +++ b/src/screens/Login/PasswordUpdatedForm.tsx @@ -39,7 +39,7 @@ export const PasswordUpdatedForm = ({ accessibilityHint={_(msg`Closes password update alert`)} variant="solid" color="primary" - size="medium"> + size="large"> Okay diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx index 88f7ec5416..a6658621cc 100644 --- a/src/screens/Login/SetNewPasswordForm.tsx +++ b/src/screens/Login/SetNewPasswordForm.tsx @@ -160,7 +160,7 @@ export const SetNewPasswordForm = ({ label={_(msg`Back`)} variant="solid" color="secondary" - size="medium" + size="large" onPress={onPressBack}> Back @@ -174,7 +174,7 @@ export const SetNewPasswordForm = ({ label={_(msg`Next`)} variant="solid" color="primary" - size="medium" + size="large" onPress={onPressNext}> Next diff --git a/src/screens/Messages/Conversation/ChatDisabled.tsx b/src/screens/Messages/Conversation/ChatDisabled.tsx index 23acc41cde..c768d2504b 100644 --- a/src/screens/Messages/Conversation/ChatDisabled.tsx +++ b/src/screens/Messages/Conversation/ChatDisabled.tsx @@ -128,7 +128,7 @@ function DialogInner() { testID="backBtn" variant="solid" color="secondary" - size="medium" + size="large" onPress={onBack} label={_(msg`Back`)}> {_(msg`Back`)} @@ -137,7 +137,7 @@ function DialogInner() { testID="submitBtn" variant="solid" color="primary" - size="medium" + size="large" onPress={onSubmit} label={_(msg`Submit`)}> {_(msg`Submit`)} diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index e782395808..efd717f0b4 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -198,7 +198,7 @@ export function MessagesScreen({navigation, route}: Props) { diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index e0a7912fd7..2cdb4b7224 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -3,9 +3,11 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import * as EmailValidator from 'email-validator' +import type tldts from 'tldts' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' +import {isEmailMaybeInvalid} from 'lib/strings/email' import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {is13, is18, useSignupContext} from '#/screens/Signup/state' import {Policies} from '#/screens/Signup/StepInfo/Policies' @@ -46,13 +48,41 @@ export function StepInfo({ const inviteCodeValueRef = useRef(state.inviteCode) const emailValueRef = useRef(state.email) + const prevEmailValueRef = useRef(state.email) const passwordValueRef = useRef(state.password) - const onNextPress = React.useCallback(async () => { + const [hasWarnedEmail, setHasWarnedEmail] = React.useState(false) + + const tldtsRef = React.useRef() + React.useEffect(() => { + // @ts-expect-error - valid path + import('tldts/dist/index.cjs.min.js').then(tldts => { + tldtsRef.current = tldts + }) + }, []) + + const onNextPress = () => { const inviteCode = inviteCodeValueRef.current const email = emailValueRef.current + const emailChanged = prevEmailValueRef.current !== email const password = passwordValueRef.current + if (emailChanged && tldtsRef.current) { + if (isEmailMaybeInvalid(email, tldtsRef.current)) { + prevEmailValueRef.current = email + setHasWarnedEmail(true) + return dispatch({ + type: 'setError', + value: _( + msg`It looks like you may have entered your email address incorrectly. Are you sure it's right?`, + ), + }) + } + } else if (hasWarnedEmail) { + setHasWarnedEmail(false) + } + prevEmailValueRef.current = email + if (!is13(state.dateOfBirth)) { return } @@ -89,13 +119,7 @@ export function StepInfo({ logEvent('signup:nextPressed', { activeStep: state.activeStep, }) - }, [ - _, - dispatch, - state.activeStep, - state.dateOfBirth, - state.serviceDescription?.inviteCodeRequired, - ]) + } return ( @@ -148,6 +172,9 @@ export function StepInfo({ testID="emailInput" onChangeText={value => { emailValueRef.current = value.trim() + if (hasWarnedEmail) { + setHasWarnedEmail(false) + } }} label={_(msg`Enter your email address`)} defaultValue={state.email} @@ -208,6 +235,7 @@ export function StepInfo({ onBackPress={onPressBack} onNextPress={onNextPress} onRetryPress={refetchServer} + overrideNextText={hasWarnedEmail ? _(msg`It's correct`) : undefined} /> ) diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index 0e1a2e61fa..3209800328 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -8,8 +8,8 @@ import {useLingui} from '@lingui/react' import {useAnalytics} from '#/lib/analytics/analytics' import {FEEDBACK_FORM_URL} from '#/lib/constants' import {useServiceQuery} from '#/state/queries/service' -import {useStarterPackQuery} from 'state/queries/starter-packs' -import {useActiveStarterPack} from 'state/shell/starter-pack' +import {useStarterPackQuery} from '#/state/queries/starter-packs' +import {useActiveStarterPack} from '#/state/shell/starter-pack' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import { initialState, @@ -132,7 +132,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { !gtMobile && {paddingBottom: 100}, ]}> - + Step {state.activeStep + 1} of{' '} {state.serviceDescription && diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx index 5f1d5e0628..68ff3aa7bc 100644 --- a/src/screens/StarterPack/StarterPackLandingScreen.tsx +++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx @@ -11,22 +11,22 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isAndroidWeb} from '#/lib/browser' import {JOINED_THIS_WEEK} from '#/lib/constants' -import {isAndroidWeb} from 'lib/browser' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {logEvent} from 'lib/statsig/statsig' -import {createStarterPackGooglePlayUri} from 'lib/strings/starter-pack' -import {isWeb} from 'platform/detection' -import {useModerationOpts} from 'state/preferences/moderation-opts' -import {useStarterPackQuery} from 'state/queries/starter-packs' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {logEvent} from '#/lib/statsig/statsig' +import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack' +import {isWeb} from '#/platform/detection' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useStarterPackQuery} from '#/state/queries/starter-packs' import { useActiveStarterPack, useSetActiveStarterPack, -} from 'state/shell/starter-pack' +} from '#/state/shell/starter-pack' +import {LoggedOutScreenState} from '#/view/com/auth/LoggedOut' import {formatCount} from '#/view/com/util/numeric/format' -import {LoggedOutScreenState} from 'view/com/auth/LoggedOut' -import {CenteredView} from 'view/com/util/Views' -import {Logo} from 'view/icons/Logo' +import {CenteredView} from '#/view/com/util/Views' +import {Logo} from '#/view/icons/Logo' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' @@ -188,12 +188,7 @@ function LandingScreenLoaded({ {record.name} + style={[a.text_center, a.font_bold, a.text_md, {color: 'white'}]}> Starter pack by {`@${creator.handle}`} @@ -219,11 +214,7 @@ function LandingScreenLoaded({ color={t.atoms.text_contrast_medium.color} /> {formatCount(i18n, JOINED_THIS_WEEK)} joined this week @@ -308,7 +299,7 @@ function LandingScreenLoaded({ label={_(msg`Signup without a starter pack`)} variant="solid" color="secondary" - size="medium" + size="large" style={[a.py_lg]} onPress={onJoinWithoutPress}> diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index 5b267ff272..0aa863f7b2 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -449,7 +449,7 @@ function Header({ }} variant="solid" color="primary" - size="medium"> + size="large"> Join Bluesky @@ -645,7 +645,7 @@ function OverflowMenu({ ) })} @@ -273,24 +264,17 @@ export const Component = observer(function EditImageImpl({ ) : null} - {adjustments.map(({label, name, onPress}) => ( - ( + ))} @@ -349,7 +333,7 @@ const styles = StyleSheet.create({ subsection: {marginTop: 12}, gap18: {gap: 18}, title: { - fontWeight: 'bold', + fontWeight: '600', fontSize: 24, }, btns: { @@ -362,12 +346,6 @@ const styles = StyleSheet.create({ paddingVertical: 8, paddingHorizontal: 24, }, - imgControl: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: 40, - }, imgEditor: { maxWidth: '100%', }, diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index 4b94aeb42f..beea3ca1a8 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -15,18 +15,18 @@ import {AppBskyActorDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useAnalytics} from '#/lib/analytics/analytics' +import {MAX_DESCRIPTION, MAX_DISPLAY_NAME} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {compressIfNeeded} from '#/lib/media/manip' +import {cleanError} from '#/lib/strings/errors' +import {enforceLen} from '#/lib/strings/helpers' +import {colors, gradients, s} from '#/lib/styles' +import {useTheme} from '#/lib/ThemeContext' import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {useProfileUpdateMutation} from '#/state/queries/profile' -import {useAnalytics} from 'lib/analytics/analytics' -import {MAX_DESCRIPTION, MAX_DISPLAY_NAME} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' -import {compressIfNeeded} from 'lib/media/manip' -import {cleanError} from 'lib/strings/errors' -import {enforceLen} from 'lib/strings/helpers' -import {colors, gradients, s} from 'lib/styles' -import {useTheme} from 'lib/ThemeContext' -import {isWeb} from 'platform/detection' import {ErrorMessage} from '../util/error/ErrorMessage' import {Text} from '../util/text/Text' import * as Toast from '../util/Toast' @@ -261,12 +261,12 @@ export function Component({ const styles = StyleSheet.create({ title: { textAlign: 'center', - fontWeight: 'bold', + fontWeight: '600', fontSize: 24, marginBottom: 18, }, label: { - fontWeight: 'bold', + fontWeight: '600', paddingHorizontal: 4, paddingBottom: 4, marginTop: 20, diff --git a/src/view/com/modals/InAppBrowserConsent.tsx b/src/view/com/modals/InAppBrowserConsent.tsx index 3fa5159346..37b039c605 100644 --- a/src/view/com/modals/InAppBrowserConsent.tsx +++ b/src/view/com/modals/InAppBrowserConsent.tsx @@ -1,19 +1,18 @@ import React from 'react' import {StyleSheet, View} from 'react-native' - -import {s} from 'lib/styles' -import {Text} from '../util/text/Text' -import {Button} from '../util/forms/Button' -import {ScrollView} from './util' -import {usePalette} from 'lib/hooks/usePalette' - import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' + +import {usePalette} from '#/lib/hooks/usePalette' +import {s} from '#/lib/styles' import {useModalControls} from '#/state/modals' import { useOpenLink, useSetInAppBrowser, } from '#/state/preferences/in-app-browser' +import {Button} from '../util/forms/Button' +import {Text} from '../util/text/Text' +import {ScrollView} from './util' export const snapPoints = [350] @@ -89,7 +88,7 @@ export function Component({href}: {href: string}) { const styles = StyleSheet.create({ title: { textAlign: 'center', - fontWeight: 'bold', + fontWeight: '600', fontSize: 24, marginBottom: 12, }, diff --git a/src/view/com/modals/UserAddRemoveLists.tsx b/src/view/com/modals/UserAddRemoveLists.tsx index f6db94ed85..29caf46609 100644 --- a/src/view/com/modals/UserAddRemoveLists.tsx +++ b/src/view/com/modals/UserAddRemoveLists.tsx @@ -9,7 +9,12 @@ import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {usePalette} from '#/lib/hooks/usePalette' +import {sanitizeDisplayName} from '#/lib/strings/display-names' import {cleanError} from '#/lib/strings/errors' +import {sanitizeHandle} from '#/lib/strings/handles' +import {s} from '#/lib/styles' +import {isAndroid, isMobileWeb, isWeb} from '#/platform/detection' import {useModalControls} from '#/state/modals' import { getMembership, @@ -19,11 +24,6 @@ import { useListMembershipRemoveMutation, } from '#/state/queries/list-memberships' import {useSession} from '#/state/session' -import {usePalette} from 'lib/hooks/usePalette' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' -import {s} from 'lib/styles' -import {isAndroid, isMobileWeb, isWeb} from 'platform/detection' import {MyLists} from '../lists/MyLists' import {Button} from '../util/forms/Button' import {Text} from '../util/text/Text' @@ -71,7 +71,7 @@ export function Component({ style={[ { textAlign: 'center', - fontWeight: 'bold', + fontWeight: '600', fontSize: 20, marginBottom: 12, paddingHorizontal: 12, diff --git a/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx b/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx index b8c125b65c..360cc0e404 100644 --- a/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx +++ b/src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx @@ -1,19 +1,20 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {ScrollView} from '../util' -import {Text} from '../../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {deviceLocales} from 'platform/detection' -import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' -import {LanguageToggle} from './LanguageToggle' -import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' import {Trans} from '@lingui/macro' + +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {deviceLanguageCodes} from '#/locale/deviceLocales' import {useModalControls} from '#/state/modals' import { useLanguagePrefs, useLanguagePrefsApi, } from '#/state/preferences/languages' +import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' +import {Text} from '../../util/text/Text' +import {ScrollView} from '../util' +import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' +import {LanguageToggle} from './LanguageToggle' export const snapPoints = ['100%'] @@ -37,10 +38,10 @@ export function Component({}: {}) { langs.sort((a, b) => { const hasA = langPrefs.contentLanguages.includes(a.code2) || - deviceLocales.includes(a.code2) + deviceLanguageCodes.includes(a.code2) const hasB = langPrefs.contentLanguages.includes(b.code2) || - deviceLocales.includes(b.code2) + deviceLanguageCodes.includes(b.code2) if (hasA === hasB) return a.name.localeCompare(b.name) if (hasA) return -1 return 1 @@ -110,7 +111,7 @@ const styles = StyleSheet.create({ }, title: { textAlign: 'center', - fontWeight: 'bold', + fontWeight: '600', fontSize: 24, marginBottom: 12, }, diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx index 05cfb81156..2b0eb8cf24 100644 --- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx +++ b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx @@ -1,20 +1,21 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {ScrollView} from '../util' -import {Text} from '../../util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {deviceLocales} from 'platform/detection' -import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' -import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' -import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {Trans} from '@lingui/macro' + +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {deviceLanguageCodes} from '#/locale/deviceLocales' import {useModalControls} from '#/state/modals' import { + hasPostLanguage, useLanguagePrefs, useLanguagePrefsApi, - hasPostLanguage, } from '#/state/preferences/languages' +import {ToggleButton} from '#/view/com/util/forms/ToggleButton' +import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' +import {Text} from '../../util/text/Text' +import {ScrollView} from '../util' +import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' export const snapPoints = ['100%'] @@ -38,10 +39,10 @@ export function Component() { langs.sort((a, b) => { const hasA = hasPostLanguage(langPrefs.postLanguage, a.code2) || - deviceLocales.includes(a.code2) + deviceLanguageCodes.includes(a.code2) const hasB = hasPostLanguage(langPrefs.postLanguage, b.code2) || - deviceLocales.includes(b.code2) + deviceLanguageCodes.includes(b.code2) if (hasA === hasB) return a.name.localeCompare(b.name) if (hasA) return -1 return 1 @@ -118,7 +119,7 @@ const styles = StyleSheet.create({ }, title: { textAlign: 'center', - fontWeight: 'bold', + fontWeight: '600', fontSize: 24, marginBottom: 12, }, diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index b1cf3b4856..5dd328062e 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -22,21 +22,21 @@ import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' +import {usePalette} from '#/lib/hooks/usePalette' +import {makeProfileLink} from '#/lib/routes/links' +import {NavigationProp} from '#/lib/routes/types' +import {forceLTR} from '#/lib/strings/bidi' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {sanitizeHandle} from '#/lib/strings/handles' +import {niceDate} from '#/lib/strings/time' +import {colors, s} from '#/lib/styles' import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {FeedNotification} from '#/state/queries/notifications/feed' -import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' -import {usePalette} from 'lib/hooks/usePalette' -import {makeProfileLink} from 'lib/routes/links' -import {NavigationProp} from 'lib/routes/types' -import {forceLTR} from 'lib/strings/bidi' -import {sanitizeDisplayName} from 'lib/strings/display-names' -import {sanitizeHandle} from 'lib/strings/handles' -import {niceDate} from 'lib/strings/time' -import {colors, s} from 'lib/styles' -import {isWeb} from 'platform/detection' -import {DM_SERVICE_HEADERS} from 'state/queries/messages/const' -import {precacheProfile} from 'state/queries/profile' -import {useAgent} from 'state/session' +import {precacheProfile} from '#/state/queries/profile' +import {useAgent} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import { @@ -315,7 +315,7 @@ let FeedItem = ({ /> {authors.length > 1 ? ( <> - + {' '} and{' '} @@ -412,7 +412,7 @@ function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileViewBasic}) { label={_(msg`Say hello!`)} variant="ghost" color="primary" - size="xsmall" + size="small" style={[a.self_center, {marginLeft: 'auto'}]} disabled={isLoading} onPress={async () => { @@ -632,7 +632,7 @@ const styles = StyleSheet.create({ alignItems: 'center', }, aviExtraCount: { - fontWeight: 'bold', + fontWeight: '600', paddingLeft: 6, }, meta: { diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index e07acef281..d6995749bf 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -5,16 +5,16 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' +import {BACK_HITSLOP} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {makeProfileLink} from '#/lib/routes/links' +import {NavigationProp} from '#/lib/routes/types' +import {sanitizeHandle} from '#/lib/strings/handles' +import {isNative} from '#/platform/detection' import {emitSoftReset} from '#/state/events' import {ImagesLightbox, useLightboxControls} from '#/state/lightbox' import {useSetDrawerOpen} from '#/state/shell' -import {BACK_HITSLOP} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {makeProfileLink} from 'lib/routes/links' -import {NavigationProp} from 'lib/routes/types' -import {sanitizeHandle} from 'lib/strings/handles' -import {isNative} from 'platform/detection' import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import {StarterPack} from '#/components/icons/StarterPack' import {TextLink} from '../util/Link' @@ -145,7 +145,7 @@ export function ProfileSubpageHeader({ testID="headerTitle" type="title-xl" href={href} - style={[pal.text, {fontWeight: 'bold'}]} + style={[pal.text, {fontWeight: '600'}]} text={title || ''} onPress={emitSoftReset} numberOfLines={4} diff --git a/src/view/com/util/Html.tsx b/src/view/com/util/Html.tsx index 2e47194811..f77fb16034 100644 --- a/src/view/com/util/Html.tsx +++ b/src/view/com/util/Html.tsx @@ -1,16 +1,17 @@ import * as React from 'react' import {StyleSheet, View} from 'react-native' -import {usePalette} from 'lib/hooks/usePalette' -import {useTheme} from 'lib/ThemeContext' -import {Text} from './text/Text' -import {TextLink} from './Link' import { H1 as ExpoH1, H2 as ExpoH2, H3 as ExpoH3, H4 as ExpoH4, } from '@expo/html-elements' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' + +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {useTheme} from '#/lib/ThemeContext' +import {TextLink} from './Link' +import {Text} from './text/Text' /** * These utilities are used to define long documents in an html-like @@ -166,7 +167,7 @@ const useStyles = () => { h4: { marginTop: 0, marginBottom: 10, - fontWeight: 'bold', + fontWeight: '600', }, p: { marginBottom: 10, diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index 4ad37ebd75..b2f56c1385 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -19,7 +19,7 @@ import {colors} from 'lib/styles' import {isAndroid, isNative, isWeb} from 'platform/detection' import {precacheProfile} from 'state/queries/profile' import {HighPriorityImage} from 'view/com/util/images/Image' -import {atoms as a, tokens, useTheme} from '#/alf' +import {tokens, useTheme} from '#/alf' import { Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, Camera_Stroke2_Corner0_Rounded as Camera, @@ -241,7 +241,13 @@ let UserAvatar = ({ onLoad={onLoad} /> )} - + {alert} ) : ( diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index ca417034db..e5121b350a 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -6,12 +6,12 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' +import {useAnalytics} from '#/lib/analytics/analytics' +import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform' +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' +import {NavigationProp} from '#/lib/routes/types' import {useSetDrawerOpen} from '#/state/shell' -import {useAnalytics} from 'lib/analytics/analytics' -import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {NavigationProp} from 'lib/routes/types' import {useTheme} from '#/alf' import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu' import {Text} from './text/Text' @@ -250,7 +250,7 @@ const styles = StyleSheet.create({ alignItems: 'center', }, title: { - fontWeight: 'bold', + fontWeight: '600', }, subtitle: { fontSize: 13, diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx index e6e05bb044..62ef2c4f1c 100644 --- a/src/view/com/util/forms/Button.tsx +++ b/src/view/com/util/forms/Button.tsx @@ -1,20 +1,21 @@ import React from 'react' import { + ActivityIndicator, GestureResponderEvent, + NativeSyntheticEvent, + NativeTouchEvent, + Pressable, + PressableStateCallbackType, StyleProp, StyleSheet, TextStyle, - Pressable, - ViewStyle, - PressableStateCallbackType, - ActivityIndicator, View, - NativeSyntheticEvent, - NativeTouchEvent, + ViewStyle, } from 'react-native' + +import {choose} from '#/lib/functions' +import {useTheme} from '#/lib/ThemeContext' import {Text} from '../text/Text' -import {useTheme} from 'lib/ThemeContext' -import {choose} from 'lib/functions' export type ButtonType = | 'primary' @@ -112,7 +113,7 @@ export function Button({ }, secondary: { color: theme.palette.secondary.text, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, default: { color: theme.palette.default.text, @@ -123,23 +124,23 @@ export function Button({ }, 'primary-outline': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-outline': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, 'primary-light': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-light': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, 'default-light': { color: theme.palette.default.text, - fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.default.isLowContrast ? '600' : undefined, }, }, ) diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index bfbafcad9d..a989cd5c5d 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -17,11 +17,11 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {HITSLOP_10} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' -import {colors} from 'lib/styles' -import {useTheme} from 'lib/ThemeContext' -import {isWeb} from 'platform/detection' +import {HITSLOP_10} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {colors} from '#/lib/styles' +import {useTheme} from '#/lib/ThemeContext' +import {isWeb} from '#/platform/detection' import {native} from '#/alf' import {FullWindowOverlay} from '#/components/FullWindowOverlay' import {Text} from '../text/Text' @@ -382,6 +382,6 @@ const styles = StyleSheet.create({ }, headingLabel: { fontSize: 18, - fontWeight: '500', + fontWeight: '600', }, }) diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx index 6668ac211f..364e10d104 100644 --- a/src/view/com/util/forms/NativeDropdown.web.tsx +++ b/src/view/com/util/forms/NativeDropdown.web.tsx @@ -5,9 +5,9 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' -import {HITSLOP_10} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' -import {useTheme} from 'lib/ThemeContext' +import {HITSLOP_10} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {useTheme} from '#/lib/ThemeContext' // Custom Dropdown Menu Components // == @@ -267,7 +267,7 @@ const styles = StyleSheet.create({ }, itemTitle: { fontSize: 16, - fontWeight: '500', + fontWeight: '600', paddingRight: 10, }, }) diff --git a/src/view/com/util/forms/RadioButton.tsx b/src/view/com/util/forms/RadioButton.tsx index 6cecd318e7..e2bf3c9ac6 100644 --- a/src/view/com/util/forms/RadioButton.tsx +++ b/src/view/com/util/forms/RadioButton.tsx @@ -1,8 +1,8 @@ import React from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' -import {choose} from 'lib/functions' -import {useTheme} from 'lib/ThemeContext' +import {choose} from '#/lib/functions' +import {useTheme} from '#/lib/ThemeContext' import {Text} from '../text/Text' import {Button, ButtonType} from './Button' @@ -86,39 +86,39 @@ export function RadioButton({ const labelStyle = choose>(type, { primary: { color: theme.palette.primary.text, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, secondary: { color: theme.palette.secondary.text, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, inverted: { color: theme.palette.inverted.text, - fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.inverted.isLowContrast ? '600' : undefined, }, 'primary-outline': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-outline': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, 'primary-light': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-light': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, default: { color: theme.palette.default.text, - fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.default.isLowContrast ? '600' : undefined, }, 'default-light': { color: theme.palette.default.text, - fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.default.isLowContrast ? '600' : undefined, }, }) return ( diff --git a/src/view/com/util/forms/ToggleButton.tsx b/src/view/com/util/forms/ToggleButton.tsx index c98e846cd3..706796fc40 100644 --- a/src/view/com/util/forms/ToggleButton.tsx +++ b/src/view/com/util/forms/ToggleButton.tsx @@ -1,11 +1,12 @@ import React from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' + +import {choose} from '#/lib/functions' +import {colors} from '#/lib/styles' +import {useTheme} from '#/lib/ThemeContext' +import {TypographyVariant} from '#/lib/ThemeContext' import {Text} from '../text/Text' import {Button, ButtonType} from './Button' -import {useTheme} from 'lib/ThemeContext' -import {choose} from 'lib/functions' -import {colors} from 'lib/styles' -import {TypographyVariant} from 'lib/ThemeContext' export function ToggleButton({ testID, @@ -100,39 +101,39 @@ export function ToggleButton({ const labelStyle = choose>(type, { primary: { color: theme.palette.primary.text, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, secondary: { color: theme.palette.secondary.text, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, inverted: { color: theme.palette.inverted.text, - fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.inverted.isLowContrast ? '600' : undefined, }, 'primary-outline': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-outline': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, 'primary-light': { color: theme.palette.primary.textInverted, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.primary.isLowContrast ? '600' : undefined, }, 'secondary-light': { color: theme.palette.secondary.textInverted, - fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.secondary.isLowContrast ? '600' : undefined, }, default: { color: theme.palette.default.text, - fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.default.isLowContrast ? '600' : undefined, }, 'default-light': { color: theme.palette.default.text, - fontWeight: theme.palette.default.isLowContrast ? '500' : undefined, + fontWeight: theme.palette.default.isLowContrast ? '600' : undefined, }, }) return ( diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 9abbe2875f..a9bfc1c966 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -88,7 +88,7 @@ export function ConstrainedImage({ void -interface GalleryItemProps { +interface Props { images: AppBskyEmbedImages.ViewImage[] index: number onPress?: EventFunction onLongPress?: EventFunction onPressIn?: EventFunction - imageStyle?: ComponentProps['style'] + imageStyle?: StyleProp viewContext?: PostEmbedViewContext + insetBorderStyle?: StyleProp } -export const GalleryItem: FC = ({ +export function GalleryItem({ images, index, imageStyle, @@ -31,7 +32,8 @@ export const GalleryItem: FC = ({ onPressIn, onLongPress, viewContext, -}) => { + insetBorderStyle, +}: Props) { const t = useTheme() const {_} = useLingui() const largeAltBadge = useLargeAltBadgeEnabled() @@ -47,7 +49,6 @@ export const GalleryItem: FC = ({ onLongPress={onLongPress ? () => onLongPress(index) : undefined} style={[ a.flex_1, - a.rounded_sm, a.overflow_hidden, t.atoms.bg_contrast_25, imageStyle, @@ -63,7 +64,7 @@ export const GalleryItem: FC = ({ accessibilityHint="" accessibilityIgnoresInvertColors /> - + {hasAlt && !hideBadges ? ( - + @@ -54,10 +63,18 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { return ( - + - + ) @@ -65,15 +82,35 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { case 3: return ( - - + + - - + + - - + + @@ -83,19 +120,51 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { return ( <> - - + + - - + + - - + + - - + + @@ -105,3 +174,22 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) { return null } } + +function noCorners( + corners: ('topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight')[], +) { + const styles: StyleProp[] = [] + if (corners.includes('topLeft')) { + styles.push({borderTopLeftRadius: 0}) + } + if (corners.includes('topRight')) { + styles.push({borderTopRightRadius: 0}) + } + if (corners.includes('bottomLeft')) { + styles.push({borderBottomLeftRadius: 0}) + } + if (corners.includes('bottomRight')) { + styles.push({borderBottomRightRadius: 0}) + } + return StyleSheet.flatten(styles) +} diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 8c4928dfcd..0ecdf25b93 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -157,7 +157,7 @@ let RepostButton = ({ label={_(msg`Cancel quote post`)} onAccessibilityEscape={close} onPress={close} - size="medium" + size="large" variant="solid" color="primary"> {_(msg`Cancel`)} diff --git a/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx b/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx index bc43e997c7..a038403b22 100644 --- a/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx +++ b/src/view/com/util/post-embeds/ActiveVideoWebContext.tsx @@ -18,7 +18,7 @@ const Context = React.createContext<{ export function Provider({children}: {children: React.ReactNode}) { if (!isWeb) { - throw new Error('ActiveVideoWebContext may onl be used on web.') + throw new Error('ActiveVideoWebContext may only be used on web.') } const [activeViewId, setActiveViewId] = useState(null) diff --git a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx index 1f966d7107..6f1c88dcdf 100644 --- a/src/view/com/util/post-embeds/ExternalGifEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalGifEmbed.tsx @@ -117,7 +117,7 @@ export function ExternalGifEmbed({ style={[ {height: imageDims.height}, styles.gifContainer, - a.rounded_sm, + a.rounded_md, a.overflow_hidden, { borderBottomLeftRadius: 0, diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx index e6ab86f9c5..54e1eb4d55 100644 --- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx @@ -59,15 +59,15 @@ export const ExternalLinkEmbed = ({ } return ( - + {imageUri && !embedPlayerParams ? ( + @@ -132,7 +132,7 @@ export function GifEmbed({ diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx index 908c06e221..3180dd99eb 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx @@ -66,8 +66,8 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { {aspectRatio}, {backgroundColor: 'black'}, a.relative, - a.rounded_sm, - a.my_xs, + a.rounded_md, + a.mt_xs, ]}>
(null) const autoplayDisabled = useAutoplayDisabled() const isWithinMessage = useIsWithinMessage() - const {muted, setMuted} = useVideoVolumeState() + const [muted, setMuted] = useVideoMuteState() const [isPlaying, setIsPlaying] = React.useState(false) const [timeRemaining, setTimeRemaining] = React.useState(0) @@ -54,16 +53,8 @@ export const VideoEmbedInnerNative = React.forwardRef( throw new Error(error) } - let aspectRatio = 16 / 9 - - if (embed.aspectRatio) { - const {width, height} = embed.aspectRatio - aspectRatio = width / height - aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1) - } - return ( - + +