Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe235a1fd9 | |||
| a21e2b45db | |||
| 8c43acc1d3 | |||
| 7782517a91 | |||
| a55444a53a | |||
| 8fc4f68e69 | |||
| 32bf8122e8 | |||
| b4d07c4112 | |||
| 1e168fd1e1 | |||
| 7e7910edf7 | |||
| af4db872b5 | |||
| 54d52b8172 | |||
| 1059088cf1 | |||
| 796d04b59f | |||
| 9bf7e278e7 | |||
| d0089a9335 | |||
| 21a5140704 | |||
| 468de4c6db | |||
| 260eb59530 | |||
| 69e5174057 | |||
| 9f070834dd | |||
| aa58216b6d | |||
| fdc3f0f17d | |||
| 0395ba3e79 | |||
| b892d8be08 | |||
| 9663b3e855 | |||
| f802f81fa6 | |||
| 30af267bb4 | |||
| b1c1c49897 | |||
| e428474478 | |||
| 4dd62e24ea | |||
| d29599fa6c | |||
| e9a792e4c1 | |||
| 631277dc3d | |||
| ac5b2cf31f | |||
| fee2f5daa2 | |||
| ba04bb58cd | |||
| 7fa47ef3c9 | |||
| ced67876cc | |||
| 247bc100cb | |||
| bc2a3aee0c | |||
| 3781074921 | |||
| 76ca72cf72 | |||
| 5d4aaa5b5f | |||
| 437bdcf9a9 | |||
| d9ba324d7f | |||
| 72326844cd | |||
| a5dbb35237 | |||
| a0acd514ab | |||
| 5028753367 | |||
| 058a29c6e3 | |||
| 09ce65b24e | |||
| c858700c17 | |||
| 625459add6 | |||
| 391e0c4c18 | |||
| dc3a42edb1 | |||
| 84724bb940 | |||
| 9f1e648686 | |||
| f67e00a85c | |||
| 48cda1350e | |||
| cc60566cde | |||
| ff23ddb556 | |||
| dff11fc076 | |||
| ad83b8303e | |||
| 6438ec6d28 | |||
| 97a4ff1a4c | |||
| 6605970fcf | |||
| 202df845f3 | |||
| 0e4f6717ce | |||
| f1c221ed28 | |||
| 78e34c8d35 | |||
| f882cf9737 | |||
| c0c8a192ce | |||
| 3c33e42d35 | |||
| 5d7cfa0206 | |||
| 103dfe2145 | |||
| 31b0d7cede | |||
| 39c9f7248e | |||
| 2b30b0f7c6 | |||
| ff5ad38253 | |||
| cfc653a5f4 | |||
| 8d5e61e1e5 | |||
| ea3fe93be9 | |||
| 9a57d2a538 | |||
| 7b6c182723 | |||
| 4f0f9eb413 | |||
| 1486e5cc5a | |||
| ee49ca7a3d | |||
| b87c94e6d9 | |||
| ec97352201 | |||
| 924f1e0e7f | |||
| 523d1f01a5 | |||
| 0b38c77faa | |||
| 93dec879ad | |||
| f139bcbcd4 | |||
| 2cb6e713bb | |||
| 6aa44d681d | |||
| 1b87351d38 | |||
| ef14faaa58 | |||
| 54a4a6c161 | |||
| 8754527aa6 | |||
| be429d5750 | |||
| 874a624e25 | |||
| 5910f78329 | |||
| b6fdde2f04 |
+3
-3
@@ -1,8 +1,9 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'@react-native-community',
|
||||
'@react-native',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-native-a11y/ios',
|
||||
'prettier',
|
||||
],
|
||||
@@ -16,9 +17,8 @@ module.exports = {
|
||||
'eslint-plugin-react-compiler',
|
||||
],
|
||||
rules: {
|
||||
// Temporary until https://github.com/facebook/react-native/pull/43756 gets into a release.
|
||||
'prettier/prettier': 0,
|
||||
'react/no-unescaped-entities': 0,
|
||||
'react/prop-types': 0,
|
||||
'react-native/no-inline-styles': 0,
|
||||
'bsky-internal/avoid-unwrapped-text': [
|
||||
'error',
|
||||
|
||||
@@ -92,6 +92,31 @@ module.exports = function (config) {
|
||||
'Used to save images to your library.',
|
||||
NSPhotoLibraryUsageDescription:
|
||||
'Used for profile pictures, posts, and other kinds of content',
|
||||
CFBundleSpokenName: 'Blue Sky',
|
||||
CFBundleLocalizations: [
|
||||
'en',
|
||||
'ca',
|
||||
'de',
|
||||
'es',
|
||||
'fi',
|
||||
'fr',
|
||||
'ga',
|
||||
'hi',
|
||||
'hu',
|
||||
'id',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'th',
|
||||
'tr',
|
||||
'uk',
|
||||
'zh_CN',
|
||||
'zh_HK',
|
||||
'zh_TW',
|
||||
],
|
||||
},
|
||||
associatedDomains: ASSOCIATED_DOMAINS,
|
||||
splash: {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 610 B |
@@ -9,7 +9,6 @@ import {
|
||||
AppBskyGraphDefs,
|
||||
AppBskyGraphStarterpack,
|
||||
AppBskyLabelerDefs,
|
||||
AtUri,
|
||||
} from '@atproto/api'
|
||||
import {ComponentChildren, h} from 'preact'
|
||||
import {useMemo} from 'preact/hooks'
|
||||
@@ -437,14 +436,14 @@ function StarterPackEmbed({
|
||||
|
||||
// from #/lib/strings/starter-pack.ts
|
||||
function getStarterPackImage(starterPack: AppBskyGraphDefs.StarterPackView) {
|
||||
const rkey = new AtUri(starterPack.uri).rkey
|
||||
const rkey = getRkey({uri: starterPack.uri})
|
||||
return `https://ogcard.cdn.bsky.app/start/${starterPack.creator.did}/${rkey}`
|
||||
}
|
||||
|
||||
function getStarterPackHref(
|
||||
starterPack: AppBskyGraphDefs.StarterPackViewBasic,
|
||||
) {
|
||||
const rkey = new AtUri(starterPack.uri).rkey
|
||||
const rkey = getRkey({uri: starterPack.uri})
|
||||
const handleOrDid = starterPack.creator.handle || starterPack.creator.did
|
||||
return `/starter-pack/${handleOrDid}/${rkey}`
|
||||
}
|
||||
|
||||
+1
-6
@@ -4209,12 +4209,7 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zod@^3.21.4:
|
||||
version "3.22.4"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
|
||||
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
|
||||
|
||||
zod@^3.23.8:
|
||||
zod@^3.21.4, zod@^3.23.8:
|
||||
version "3.23.8"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
|
||||
@@ -9,17 +9,21 @@ module.exports = {
|
||||
'fi',
|
||||
'fr',
|
||||
'ga',
|
||||
'gl',
|
||||
'hi',
|
||||
'hu',
|
||||
'id',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'nl',
|
||||
'pl',
|
||||
'pt-BR',
|
||||
'ru',
|
||||
'th',
|
||||
'tr',
|
||||
'uk',
|
||||
'vi',
|
||||
'zh-CN',
|
||||
'zh-HK',
|
||||
'zh-TW',
|
||||
|
||||
@@ -9,6 +9,11 @@ cfg.resolver.sourceExts = process.env.RN_SRC_EXT
|
||||
if (cfg.resolver.resolveRequest) {
|
||||
throw Error('Update this override because it is conflicting now.')
|
||||
}
|
||||
|
||||
if (process.env.BSKY_PROFILE) {
|
||||
cfg.cacheVersion += ':PROFILE'
|
||||
}
|
||||
|
||||
cfg.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
// HACK: manually resolve a few packages that use `exports` in `package.json`.
|
||||
// A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing.
|
||||
@@ -29,6 +34,15 @@ cfg.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
if (moduleName === '@ipld/dag-cbor') {
|
||||
return context.resolveRequest(context, '@ipld/dag-cbor/src', platform)
|
||||
}
|
||||
if (process.env.BSKY_PROFILE) {
|
||||
if (moduleName.endsWith('ReactNativeRenderer-prod')) {
|
||||
return context.resolveRequest(
|
||||
context,
|
||||
moduleName.replace('-prod', '-profiling'),
|
||||
platform,
|
||||
)
|
||||
}
|
||||
}
|
||||
return context.resolveRequest(context, moduleName, platform)
|
||||
}
|
||||
|
||||
|
||||
+29
-47
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.94.0",
|
||||
"version": "1.95.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -11,6 +11,8 @@
|
||||
"postinstall": "patch-package && yarn intl:compile",
|
||||
"prebuild": "expo prebuild --clean",
|
||||
"android": "expo run:android",
|
||||
"android:prod": "expo run:android --variant release",
|
||||
"android:profile": "BSKY_PROFILE=1 expo run:android --variant release",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"use-build-number": "./scripts/useBuildNumberEnv.sh",
|
||||
@@ -23,7 +25,6 @@
|
||||
"build-embed": "cd bskyembed && yarn build && yarn build-snippet && cd .. && node ./scripts/post-embed-build.js",
|
||||
"start": "expo start --dev-client",
|
||||
"start:prod": "expo start --dev-client --no-dev --minify",
|
||||
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
|
||||
"test": "NODE_ENV=test jest --forceExit --testTimeout=20000 --bail",
|
||||
"test-watch": "NODE_ENV=test jest --watchAll",
|
||||
"test-ci": "NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit",
|
||||
@@ -53,7 +54,7 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.11",
|
||||
"@atproto/api": "^0.13.18",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
@@ -89,18 +90,18 @@
|
||||
"@tanstack/query-async-storage-persister": "^5.25.0",
|
||||
"@tanstack/react-query": "^5.8.1",
|
||||
"@tanstack/react-query-persist-client": "^5.25.0",
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/extension-document": "^2.6.6",
|
||||
"@tiptap/extension-hard-break": "^2.6.6",
|
||||
"@tiptap/extension-history": "^2.6.6",
|
||||
"@tiptap/extension-mention": "^2.6.6",
|
||||
"@tiptap/extension-paragraph": "^2.6.6",
|
||||
"@tiptap/extension-placeholder": "^2.6.6",
|
||||
"@tiptap/extension-text": "^2.6.6",
|
||||
"@tiptap/html": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6",
|
||||
"@tiptap/react": "^2.6.6",
|
||||
"@tiptap/suggestion": "^2.6.6",
|
||||
"@tiptap/core": "^2.9.1",
|
||||
"@tiptap/extension-document": "^2.9.1",
|
||||
"@tiptap/extension-hard-break": "^2.9.1",
|
||||
"@tiptap/extension-history": "^2.9.1",
|
||||
"@tiptap/extension-mention": "^2.9.1",
|
||||
"@tiptap/extension-paragraph": "^2.9.1",
|
||||
"@tiptap/extension-placeholder": "^2.9.1",
|
||||
"@tiptap/extension-text": "^2.9.1",
|
||||
"@tiptap/html": "^2.9.1",
|
||||
"@tiptap/pm": "^2.9.1",
|
||||
"@tiptap/react": "^2.9.1",
|
||||
"@tiptap/suggestion": "^2.9.1",
|
||||
"@types/invariant": "^2.2.37",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/node": "^20.14.3",
|
||||
@@ -140,6 +141,7 @@
|
||||
"expo-sharing": "^12.0.1",
|
||||
"expo-splash-screen": "~0.27.4",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "^3.0.4",
|
||||
"expo-task-manager": "~11.8.1",
|
||||
"expo-updates": "~0.25.14",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
@@ -152,11 +154,6 @@
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"lodash.once": "^4.1.1",
|
||||
"lodash.random": "^3.2.0",
|
||||
"lodash.samplesize": "^4.2.0",
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"multiformats": "^13.1.0",
|
||||
@@ -166,7 +163,7 @@
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"psl": "^1.9.0",
|
||||
"react": "18.2.0",
|
||||
"react-compiler-runtime": "^19.0.0-beta-a7bf2bd-20241110",
|
||||
"react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-keyed-flatten-children": "^3.0.0",
|
||||
@@ -207,7 +204,7 @@
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@atproto/dev-env": "^0.3.55",
|
||||
"@atproto/dev-env": "^0.3.64",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/preset-env": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
@@ -217,45 +214,35 @@
|
||||
"@lingui/cli": "^4.5.0",
|
||||
"@lingui/macro": "^4.5.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
|
||||
"@react-native-community/eslint-config": "^3.0.0",
|
||||
"@react-native/eslint-config": "^0.76.2",
|
||||
"@react-native/typescript-config": "^0.74.1",
|
||||
"@testing-library/jest-native": "^5.4.1",
|
||||
"@testing-library/react-native": "^11.5.2",
|
||||
"@tsconfig/react-native": "^2.0.3",
|
||||
"@types/he": "^1.1.2",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash.chunk": "^4.2.7",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.isequal": "^4.5.6",
|
||||
"@types/lodash.omit": "^4.5.7",
|
||||
"@types/lodash.once": "^4.1.7",
|
||||
"@types/lodash.random": "^3.2.7",
|
||||
"@types/lodash.samplesize": "^4.2.7",
|
||||
"@types/lodash.set": "^4.3.7",
|
||||
"@types/lodash.shuffle": "^4.2.7",
|
||||
"@types/psl": "^1.1.1",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-responsive": "^8.0.5",
|
||||
"@types/react-test-renderer": "^17.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
|
||||
"babel-plugin-react-native-web": "^0.18.12",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"babel-preset-expo": "^10.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-bsky-internal": "link:./eslint",
|
||||
"eslint-plugin-ft-flow": "^2.0.3",
|
||||
"eslint-plugin-lingui": "^0.2.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
|
||||
"eslint-plugin-react-native-a11y": "^3.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"file-loader": "6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"husky": "^8.0.3",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest": "^29.7.0",
|
||||
@@ -269,21 +256,16 @@
|
||||
"react-test-renderer": "18.2.0",
|
||||
"svgo": "^3.3.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.5.4",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
"typescript": "^5.7.2",
|
||||
"webpack-bundle-analyzer": "^4.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@radix-ui/react-focus-scope": "1.1.0",
|
||||
"@react-native/babel-preset": "0.74.1",
|
||||
"@types/react": "^18",
|
||||
"**/zeed-dom": "0.10.9",
|
||||
"**/zod": "3.23.8",
|
||||
"**/expo-constants": "16.0.1",
|
||||
"**/expo-device": "6.0.2",
|
||||
"@react-native/babel-preset": "0.74.1",
|
||||
"@radix-ui/react-focus-scope": "1.1.0"
|
||||
"**/zod": "3.23.8"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo/ios",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
diff --git a/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java b/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
|
||||
index 06829bd..1b15818 100644
|
||||
--- a/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
|
||||
+++ b/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
|
||||
@@ -14,17 +14,33 @@ import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.HashMap;
|
||||
+
|
||||
+class ParsedPath {
|
||||
+ final Path path;
|
||||
+ final ArrayList<PathElement> elements;
|
||||
+
|
||||
+ ParsedPath(Path path, ArrayList<PathElement> elements) {
|
||||
+ this.path = path;
|
||||
+ this.elements = elements;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
@SuppressLint("ViewConstructor")
|
||||
class PathView extends RenderableView {
|
||||
private Path mPath;
|
||||
|
||||
+ // This grows forever but for our use case (static icons) it's ok.
|
||||
+ private static final HashMap<String, ParsedPath> sPathCache = new HashMap<>();
|
||||
+
|
||||
public PathView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
PathParser.mScale = mScale;
|
||||
mPath = new Path();
|
||||
}
|
||||
|
||||
- public void setD(String d) {
|
||||
+ void setDByParsing(String d) {
|
||||
mPath = PathParser.parse(d);
|
||||
elements = PathParser.elements;
|
||||
for (PathElement elem : elements) {
|
||||
@@ -33,6 +49,17 @@ class PathView extends RenderableView {
|
||||
point.y *= mScale;
|
||||
}
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ public void setD(String d) {
|
||||
+ ParsedPath cached = sPathCache.get(d);
|
||||
+ if (cached != null) {
|
||||
+ mPath = cached.path;
|
||||
+ elements = cached.elements;
|
||||
+ } else {
|
||||
+ setDByParsing(d);
|
||||
+ sPathCache.put(d, new ParsedPath(mPath, elements));
|
||||
+ }
|
||||
invalidate();
|
||||
}
|
||||
|
||||
+37
-34
@@ -69,6 +69,7 @@ import {Provider as PortalProvider} from '#/components/Portal'
|
||||
import {Splash} from '#/Splash'
|
||||
import {BottomSheetProvider} from '../modules/bottom-sheet'
|
||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||
import {AppProfiler} from './AppProfiler'
|
||||
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
@@ -184,40 +185,42 @@ function App() {
|
||||
* that is set up in the InnerApp component above.
|
||||
*/
|
||||
return (
|
||||
<GeolocationProvider>
|
||||
<A11yProvider>
|
||||
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
|
||||
<SessionProvider>
|
||||
<PrefsStateProvider>
|
||||
<I18nProvider>
|
||||
<ShellStateProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<BottomSheetProvider>
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<IntentDialogProvider>
|
||||
<InnerApp />
|
||||
</IntentDialogProvider>
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</ShellStateProvider>
|
||||
</I18nProvider>
|
||||
</PrefsStateProvider>
|
||||
</SessionProvider>
|
||||
</KeyboardProvider>
|
||||
</A11yProvider>
|
||||
</GeolocationProvider>
|
||||
<AppProfiler>
|
||||
<GeolocationProvider>
|
||||
<A11yProvider>
|
||||
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
|
||||
<SessionProvider>
|
||||
<PrefsStateProvider>
|
||||
<I18nProvider>
|
||||
<ShellStateProvider>
|
||||
<InvitesStateProvider>
|
||||
<ModalStateProvider>
|
||||
<DialogStateProvider>
|
||||
<LightboxStateProvider>
|
||||
<PortalProvider>
|
||||
<BottomSheetProvider>
|
||||
<StarterPackProvider>
|
||||
<SafeAreaProvider
|
||||
initialMetrics={initialWindowMetrics}>
|
||||
<IntentDialogProvider>
|
||||
<InnerApp />
|
||||
</IntentDialogProvider>
|
||||
</SafeAreaProvider>
|
||||
</StarterPackProvider>
|
||||
</BottomSheetProvider>
|
||||
</PortalProvider>
|
||||
</LightboxStateProvider>
|
||||
</DialogStateProvider>
|
||||
</ModalStateProvider>
|
||||
</InvitesStateProvider>
|
||||
</ShellStateProvider>
|
||||
</I18nProvider>
|
||||
</PrefsStateProvider>
|
||||
</SessionProvider>
|
||||
</KeyboardProvider>
|
||||
</A11yProvider>
|
||||
</GeolocationProvider>
|
||||
</AppProfiler>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React, {Profiler} from 'react'
|
||||
|
||||
// Don't let it get stripped out in profiling builds (which apply production Babel preset).
|
||||
const log = (global as any)['con' + 'sole'].log
|
||||
|
||||
function onRender(id: string, phase: string, actualDuration: number) {
|
||||
if (!__DEV__) {
|
||||
// This block of code will exist in the production build of the app.
|
||||
// However, only profiling builds of React call `onRender` so it's dead code in actual production.
|
||||
const message = `<Profiler> ${id}:${phase} ${
|
||||
actualDuration > 500
|
||||
? '(╯°□°)╯ '
|
||||
: actualDuration > 100
|
||||
? '[!!] '
|
||||
: actualDuration > 16
|
||||
? '[!] '
|
||||
: ''
|
||||
}${Math.round(actualDuration)}ms`
|
||||
log(message)
|
||||
}
|
||||
}
|
||||
|
||||
export function AppProfiler({children}: {children: React.ReactNode}) {
|
||||
return (
|
||||
<Profiler id="app" onRender={onRender}>
|
||||
{children}
|
||||
</Profiler>
|
||||
)
|
||||
}
|
||||
+1
-7
@@ -32,7 +32,7 @@ import {
|
||||
import {RouteParams, State} from '#/lib/routes/types'
|
||||
import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
|
||||
import {bskyTitle} from '#/lib/strings/headings'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||
import {useSession} from '#/state/session'
|
||||
@@ -453,7 +453,6 @@ function HomeTabNavigator() {
|
||||
return (
|
||||
<HomeTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
@@ -472,7 +471,6 @@ function SearchTabNavigator() {
|
||||
return (
|
||||
<SearchTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
@@ -490,7 +488,6 @@ function NotificationsTabNavigator() {
|
||||
return (
|
||||
<NotificationsTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
@@ -512,7 +509,6 @@ function MyProfileTabNavigator() {
|
||||
return (
|
||||
<MyProfileTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
@@ -538,7 +534,6 @@ function MessagesTabNavigator() {
|
||||
return (
|
||||
<MessagesTab.Navigator
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
@@ -572,7 +567,6 @@ const FlatNavigator = () => {
|
||||
<Flat.Navigator
|
||||
screenListeners={screenListeners}
|
||||
screenOptions={{
|
||||
animation: isAndroid ? 'ios' : undefined,
|
||||
animationDuration: 285,
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, {Children} from 'react'
|
||||
import {TextProps as RNTextProps} from 'react-native'
|
||||
import {StyleProp, TextStyle} from 'react-native'
|
||||
import {UITextView} from 'react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {Alf, applyFonts, atoms, flatten} from '#/alf'
|
||||
|
||||
/**
|
||||
* Util to calculate lineHeight from a text size atom and a leading atom
|
||||
*
|
||||
* Example:
|
||||
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
|
||||
*/
|
||||
export function leading<
|
||||
Size extends {fontSize?: number},
|
||||
Leading extends {lineHeight?: number},
|
||||
>(textSize: Size, leading: Leading) {
|
||||
const size = textSize?.fontSize || atoms.text_md.fontSize
|
||||
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
|
||||
return Math.round(size * lineHeight)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
|
||||
* other relative leading atoms.
|
||||
*
|
||||
* If the `lineHeight` value is > 2, we assume it's an absolute value and
|
||||
* returns it as-is.
|
||||
*/
|
||||
export function normalizeTextStyles(
|
||||
styles: StyleProp<TextStyle>,
|
||||
{
|
||||
fontScale,
|
||||
fontFamily,
|
||||
}: {
|
||||
fontScale: number
|
||||
fontFamily: Alf['fonts']['family']
|
||||
} & Pick<Alf, 'flags'>,
|
||||
) {
|
||||
const s = flatten(styles)
|
||||
// should always be defined on these components
|
||||
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
|
||||
|
||||
if (s?.lineHeight) {
|
||||
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
|
||||
s.lineHeight = Math.round(s.fontSize * s.lineHeight)
|
||||
}
|
||||
} else if (!isNative) {
|
||||
s.lineHeight = s.fontSize
|
||||
}
|
||||
|
||||
applyFonts(s, fontFamily)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
export type StringChild = string | (string | null)[]
|
||||
export type TextProps = RNTextProps & {
|
||||
/**
|
||||
* Lets the user select text, to use the native copy and paste functionality.
|
||||
*/
|
||||
selectable?: boolean
|
||||
/**
|
||||
* Provides `data-*` attributes to the underlying `UITextView` component on
|
||||
* web only.
|
||||
*/
|
||||
dataSet?: Record<string, string | number | undefined>
|
||||
/**
|
||||
* Appears as a small tooltip on web hover.
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Whether the children could possibly contain emoji.
|
||||
*/
|
||||
emoji?: boolean
|
||||
}
|
||||
|
||||
const EMOJI = createEmojiRegex()
|
||||
|
||||
export function childHasEmoji(children: React.ReactNode) {
|
||||
let hasEmoji = false
|
||||
Children.forEach(children, child => {
|
||||
if (typeof child === 'string' && createEmojiRegex().test(child)) {
|
||||
hasEmoji = true
|
||||
}
|
||||
})
|
||||
return hasEmoji
|
||||
}
|
||||
|
||||
export function renderChildrenWithEmoji(
|
||||
children: React.ReactNode,
|
||||
props: Omit<TextProps, 'children'> = {},
|
||||
emoji: boolean,
|
||||
) {
|
||||
if (!isIOS || !emoji) {
|
||||
return children
|
||||
}
|
||||
return Children.map(children, child => {
|
||||
if (typeof child !== 'string') return child
|
||||
|
||||
const emojis = child.match(EMOJI)
|
||||
|
||||
if (emojis === null) {
|
||||
return child
|
||||
}
|
||||
|
||||
return child.split(EMOJI).map((stringPart, index) => [
|
||||
stringPart,
|
||||
emojis[index] ? (
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null,
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
const SINGLE_EMOJI_RE = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u
|
||||
export function isOnlyEmoji(text: string) {
|
||||
return text.length <= 15 && SINGLE_EMOJI_RE.test(text)
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {moderateProfile} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -458,7 +458,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
// @ts-ignore - this will always be a pressable
|
||||
ref={ref}
|
||||
aria-label={label}
|
||||
aria-pressed={state.pressed}
|
||||
accessibilityLabel={label}
|
||||
disabled={disabled || false}
|
||||
accessibilityState={{
|
||||
|
||||
@@ -75,7 +75,7 @@ export function Outer({
|
||||
try {
|
||||
cb()
|
||||
} catch (e: any) {
|
||||
logger.error('Error running close callback', e)
|
||||
logger.error(e || 'Error running close callback')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||
@@ -42,7 +41,6 @@ export function Outer({
|
||||
onClose,
|
||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const {setDialogIsOpen} = useDialogStateControlContext()
|
||||
@@ -118,16 +116,7 @@ export function Outer({
|
||||
gtMobile ? a.p_lg : a.p_md,
|
||||
{overflowY: 'auto'},
|
||||
]}>
|
||||
<Animated.View
|
||||
entering={FadeIn.duration(150)}
|
||||
// exiting={FadeOut.duration(150)}
|
||||
style={[
|
||||
web(a.fixed),
|
||||
a.inset_0,
|
||||
{opacity: 0.8, backgroundColor: t.palette.black},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Backdrop />
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
@@ -164,7 +153,7 @@ export function Inner({
|
||||
useFocusGuards()
|
||||
return (
|
||||
<FocusScope loop asChild trapped>
|
||||
<Animated.View
|
||||
<View
|
||||
role="dialog"
|
||||
aria-role="dialog"
|
||||
aria-label={label}
|
||||
@@ -174,8 +163,6 @@ export function Inner({
|
||||
onClick={stopPropagation}
|
||||
onStartShouldSetResponder={_ => true}
|
||||
onTouchEnd={stopPropagation}
|
||||
entering={FadeInDown.duration(100)}
|
||||
// exiting={FadeOut.duration(100)}
|
||||
style={flatten([
|
||||
a.relative,
|
||||
a.rounded_md,
|
||||
@@ -188,6 +175,8 @@ export function Inner({
|
||||
shadowColor: t.palette.black,
|
||||
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||
shadowRadius: 30,
|
||||
// @ts-ignore web only
|
||||
animation: 'fadeIn ease-out 0.1s',
|
||||
},
|
||||
flatten(style),
|
||||
])}>
|
||||
@@ -201,7 +190,7 @@ export function Inner({
|
||||
{children}
|
||||
</View>
|
||||
</DismissableLayer>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</FocusScope>
|
||||
)
|
||||
}
|
||||
@@ -268,3 +257,25 @@ export function Close() {
|
||||
export function Handle() {
|
||||
return null
|
||||
}
|
||||
|
||||
function Backdrop() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
opacity: 0.8,
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.fixed,
|
||||
a.inset_0,
|
||||
{
|
||||
backgroundColor: t.palette.black,
|
||||
// @ts-ignore web only
|
||||
animation: 'fadeIn ease-out 0.15s',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function Header({
|
||||
@@ -29,10 +29,8 @@ export function Header({
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
web([
|
||||
{borderRadiusTopLeft: a.rounded_md.borderRadius},
|
||||
{borderRadiusTopRight: a.rounded_md.borderRadius},
|
||||
]),
|
||||
{borderTopLeftRadius: a.rounded_md.borderRadius},
|
||||
{borderTopRightRadius: a.rounded_md.borderRadius},
|
||||
style,
|
||||
]}>
|
||||
{renderLeft && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, flatten, useTheme, ViewStyleProp} from '#/alf'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {LinearGradient} from 'expo-linear-gradient'
|
||||
|
||||
import {atoms as a, tokens} from '#/alf'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useMemo} from 'react'
|
||||
import {useCallback, useMemo} from 'react'
|
||||
import {ActivityIndicator, FlatList, View} from 'react-native'
|
||||
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
+9
-13
@@ -223,7 +223,7 @@ export function Link({
|
||||
{...web({
|
||||
hrefAttrs: {
|
||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
||||
rel: isExternal ? 'noopener' : undefined,
|
||||
download,
|
||||
},
|
||||
dataSet: {
|
||||
@@ -274,11 +274,6 @@ export function InlineLinkText({
|
||||
onOut: onHoverOut,
|
||||
} = useInteractionState()
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const {
|
||||
state: pressed,
|
||||
onIn: onPressIn,
|
||||
onOut: onPressOut,
|
||||
} = useInteractionState()
|
||||
const flattenedStyle = flatten(style) || {}
|
||||
|
||||
return (
|
||||
@@ -289,19 +284,20 @@ export function InlineLinkText({
|
||||
{...rest}
|
||||
style={[
|
||||
{color: t.palette.primary_500},
|
||||
(hovered || focused || pressed) &&
|
||||
(hovered || focused) &&
|
||||
!disableUnderline && {
|
||||
...web({outline: 0}),
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
|
||||
...web({
|
||||
outline: 0,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor:
|
||||
flattenedStyle.color ?? t.palette.primary_500,
|
||||
}),
|
||||
},
|
||||
flattenedStyle,
|
||||
]}
|
||||
role="link"
|
||||
onPress={download ? undefined : onPress}
|
||||
onLongPress={onLongPress}
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onMouseEnter={onHoverIn}
|
||||
@@ -311,7 +307,7 @@ export function InlineLinkText({
|
||||
{...web({
|
||||
hrefAttrs: {
|
||||
target: download ? undefined : isExternal ? 'blank' : undefined,
|
||||
rel: isExternal ? 'noopener noreferrer' : undefined,
|
||||
rel: isExternal ? 'noopener' : undefined,
|
||||
download,
|
||||
},
|
||||
dataSet: {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, flatten, useTheme} from '#/alf'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import React from 'react'
|
||||
import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
|
||||
@@ -302,8 +302,8 @@ export function ProfileHoverCardInner(props: ProfileHoverCardProps) {
|
||||
const animationStyle = {
|
||||
animation:
|
||||
currentState.stage === 'hiding'
|
||||
? `avatarHoverFadeOut ${HIDE_DURATION}ms both`
|
||||
: `avatarHoverFadeIn ${SHOW_DURATION}ms both`,
|
||||
? `fadeOut ${HIDE_DURATION}ms both`
|
||||
: `fadeIn ${SHOW_DURATION}ms both`,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import * as Progress from 'react-native-progress'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
@@ -9,6 +9,7 @@ import {NavigationProp} from '#/lib/routes/types'
|
||||
import {toShortUrl} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf'
|
||||
import {isOnlyEmoji} from '#/alf/typography'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {InlineLinkText, LinkProps} from '#/components/Link'
|
||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||
@@ -53,7 +54,6 @@ export function RichText({
|
||||
const plainStyles = [a.leading_snug, flattenedStyle]
|
||||
const interactiveStyles = [
|
||||
a.leading_snug,
|
||||
a.pointer_events_auto,
|
||||
flatten(interactiveStyle),
|
||||
flattenedStyle,
|
||||
]
|
||||
@@ -151,17 +151,14 @@ export function RichText({
|
||||
/>,
|
||||
)
|
||||
} else {
|
||||
els.push(
|
||||
<Text key={key} emoji style={plainStyles}>
|
||||
{segment.text}
|
||||
</Text>,
|
||||
)
|
||||
els.push(segment.text)
|
||||
}
|
||||
key++
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
emoji
|
||||
selectable={selectable}
|
||||
testID={testID}
|
||||
style={plainStyles}
|
||||
@@ -194,11 +191,6 @@ function RichTextTag({
|
||||
onOut: onHoverOut,
|
||||
} = useInteractionState()
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const {
|
||||
state: pressed,
|
||||
onIn: onPressIn,
|
||||
onOut: onPressOut,
|
||||
} = useInteractionState()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
const navigateToPage = React.useCallback(() => {
|
||||
@@ -228,8 +220,6 @@ function RichTextTag({
|
||||
accessibilityRole: isNative ? 'button' : undefined,
|
||||
onPress: navigateToPage,
|
||||
onLongPress: openDialog,
|
||||
onPressIn: onPressIn,
|
||||
onPressOut: onPressOut,
|
||||
})}
|
||||
{...web({
|
||||
onMouseEnter: onHoverIn,
|
||||
@@ -243,10 +233,12 @@ function RichTextTag({
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{color: t.palette.primary_500},
|
||||
(hovered || focused || pressed) && {
|
||||
...web({outline: 0}),
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: t.palette.primary_500,
|
||||
(hovered || focused) && {
|
||||
...web({
|
||||
outline: 0,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationColor: t.palette.primary_500,
|
||||
}),
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
@@ -256,10 +248,3 @@ function RichTextTag({
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export function isOnlyEmoji(text: string) {
|
||||
return (
|
||||
text.length <= 15 &&
|
||||
/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u.test(text)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
|
||||
ref,
|
||||
) {
|
||||
const t = useTheme()
|
||||
const bottomBarOffset = useBottomBarOffset(300)
|
||||
const bottomBarOffset = useBottomBarOffset(headerHeight)
|
||||
const initialNumToRender = useInitialNumToRender()
|
||||
const {currentAccount} = useSession()
|
||||
const {data, refetch, isError} = useAllListMembersQuery(listUri)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useRef} from 'react'
|
||||
import {useRef} from 'react'
|
||||
import type {ListRenderItemInfo} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {Keyboard, View} from 'react-native'
|
||||
import {
|
||||
AppBskyActorDefs,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
|
||||
import {isTouchDevice} from '#/lib/browser'
|
||||
|
||||
+164
-150
@@ -40,9 +40,37 @@ export function TagMenu({
|
||||
tag: string
|
||||
authorHandle?: string
|
||||
}>) {
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
<TagMenuInner
|
||||
control={control}
|
||||
tag={tag}
|
||||
authorHandle={authorHandle}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TagMenuInner({
|
||||
control,
|
||||
tag,
|
||||
authorHandle,
|
||||
navigation,
|
||||
}: {
|
||||
control: Dialog.DialogOuterProps['control']
|
||||
tag: string
|
||||
authorHandle?: string
|
||||
// Passed down because on native, we don't use real portals (and context would be wrong).
|
||||
navigation: NavigationProp
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {isLoading: isPreferencesLoading, data: preferences} =
|
||||
usePreferencesQuery()
|
||||
const {
|
||||
@@ -79,32 +107,75 @@ export function TagMenu({
|
||||
}, [tag, preferences?.moderationPrefs?.mutedWords])
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
|
||||
{isPreferencesLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
|
||||
{isPreferencesLoading ? (
|
||||
<View style={[a.w_full, a.align_center]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
a.mb_md,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Link
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
})
|
||||
})
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.border,
|
||||
a.mb_md,
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg_contrast_25,
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
See{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
|
||||
{authorHandle && !isInvalidHandle(authorHandle) && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Link
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
label={_(
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
})}>
|
||||
@@ -118,7 +189,7 @@ export function TagMenu({
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Person size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
@@ -134,143 +205,86 @@ export function TagMenu({
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts
|
||||
posts by this user
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{authorHandle && !isInvalidHandle(authorHandle) && (
|
||||
<>
|
||||
<Divider />
|
||||
{preferences ? (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Link
|
||||
label={_(
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Person
|
||||
size="lg"
|
||||
style={[t.atoms.text_contrast_medium]}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
<Trans>
|
||||
See{' '}
|
||||
<Text
|
||||
style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
posts by this user
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{preferences ? (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Button
|
||||
label={
|
||||
isMuted
|
||||
? _(msg`Unmute all ${displayTag} posts`)
|
||||
: _(msg`Mute all ${displayTag} posts`)
|
||||
<Button
|
||||
label={
|
||||
isMuted
|
||||
? _(msg`Unmute all ${displayTag} posts`)
|
||||
: _(msg`Mute all ${displayTag} posts`)
|
||||
}
|
||||
onPress={() => {
|
||||
control.close(() => {
|
||||
if (isMuted) {
|
||||
resetUpsert()
|
||||
removeMutedWords(removeableMuteWords)
|
||||
} else {
|
||||
resetRemove()
|
||||
upsertMutedWord([
|
||||
{
|
||||
value: tag,
|
||||
targets: ['tag'],
|
||||
actorTarget: 'all',
|
||||
},
|
||||
])
|
||||
}
|
||||
onPress={() => {
|
||||
control.close(() => {
|
||||
if (isMuted) {
|
||||
resetUpsert()
|
||||
removeMutedWords(removeableMuteWords)
|
||||
} else {
|
||||
resetRemove()
|
||||
upsertMutedWord([
|
||||
{
|
||||
value: tag,
|
||||
targets: ['tag'],
|
||||
actorTarget: 'all',
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Mute
|
||||
size="lg"
|
||||
style={[t.atoms.text_contrast_medium]}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
<Trans>posts</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
})
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_start,
|
||||
a.gap_md,
|
||||
a.px_lg,
|
||||
a.py_md,
|
||||
]}>
|
||||
<Mute size="lg" style={[t.atoms.text_contrast_medium]} />
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="middle"
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
a.font_bold,
|
||||
native({top: 2}),
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
|
||||
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
|
||||
{displayTag}
|
||||
</Text>{' '}
|
||||
<Trans>posts</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
label={_(msg`Close this dialog`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Inner>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
<Button
|
||||
label={_(msg`Close this dialog`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
onPress={() => control.close()}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Dialog.Inner>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-139
@@ -1,140 +1,15 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
|
||||
import {UITextView} from 'react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS, isNative} from '#/platform/detection'
|
||||
import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
|
||||
import {atoms, flatten, useAlf, useTheme, web} from '#/alf'
|
||||
import {
|
||||
childHasEmoji,
|
||||
normalizeTextStyles,
|
||||
renderChildrenWithEmoji,
|
||||
TextProps,
|
||||
} from '#/alf/typography'
|
||||
import {IS_DEV} from '#/env'
|
||||
|
||||
export type StringChild = string | (string | null)[]
|
||||
|
||||
export type TextProps = Omit<RNTextProps, 'children'> & {
|
||||
/**
|
||||
* Lets the user select text, to use the native copy and paste functionality.
|
||||
*/
|
||||
selectable?: boolean
|
||||
/**
|
||||
* Provides `data-*` attributes to the underlying `UITextView` component on
|
||||
* web only.
|
||||
*/
|
||||
dataSet?: Record<string, string | number | undefined>
|
||||
/**
|
||||
* Appears as a small tooltip on web hover.
|
||||
*/
|
||||
title?: string
|
||||
} & (
|
||||
| {
|
||||
emoji?: true
|
||||
children: StringChild
|
||||
}
|
||||
| {
|
||||
emoji?: false
|
||||
children: RNTextProps['children']
|
||||
}
|
||||
)
|
||||
|
||||
const EMOJI = createEmojiRegex()
|
||||
|
||||
export function childHasEmoji(children: React.ReactNode) {
|
||||
return (Array.isArray(children) ? children : [children]).some(
|
||||
child => typeof child === 'string' && createEmojiRegex().test(child),
|
||||
)
|
||||
}
|
||||
|
||||
export function childIsString(
|
||||
children: React.ReactNode,
|
||||
): children is StringChild {
|
||||
return (
|
||||
typeof children === 'string' ||
|
||||
(Array.isArray(children) &&
|
||||
children.every(child => typeof child === 'string' || child === null))
|
||||
)
|
||||
}
|
||||
|
||||
export function renderChildrenWithEmoji(
|
||||
children: StringChild,
|
||||
props: Omit<TextProps, 'children'> = {},
|
||||
) {
|
||||
const normalized = Array.isArray(children) ? children : [children]
|
||||
|
||||
return (
|
||||
<UITextView {...props}>
|
||||
{normalized.map(child => {
|
||||
if (typeof child !== 'string') return child
|
||||
|
||||
const emojis = child.match(EMOJI)
|
||||
|
||||
if (emojis === null) {
|
||||
return child
|
||||
}
|
||||
|
||||
return child.split(EMOJI).map((stringPart, index) => (
|
||||
<UITextView key={index} {...props}>
|
||||
{stringPart}
|
||||
{emojis[index] ? (
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null}
|
||||
</UITextView>
|
||||
))
|
||||
})}
|
||||
</UITextView>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Util to calculate lineHeight from a text size atom and a leading atom
|
||||
*
|
||||
* Example:
|
||||
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
|
||||
*/
|
||||
export function leading<
|
||||
Size extends {fontSize?: number},
|
||||
Leading extends {lineHeight?: number},
|
||||
>(textSize: Size, leading: Leading) {
|
||||
const size = textSize?.fontSize || atoms.text_md.fontSize
|
||||
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
|
||||
return Math.round(size * lineHeight)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
|
||||
* other relative leading atoms.
|
||||
*
|
||||
* If the `lineHeight` value is > 2, we assume it's an absolute value and
|
||||
* returns it as-is.
|
||||
*/
|
||||
export function normalizeTextStyles(
|
||||
styles: StyleProp<TextStyle>,
|
||||
{
|
||||
fontScale,
|
||||
fontFamily,
|
||||
}: {
|
||||
fontScale: number
|
||||
fontFamily: Alf['fonts']['family']
|
||||
} & Pick<Alf, 'flags'>,
|
||||
) {
|
||||
const s = flatten(styles)
|
||||
// should always be defined on these components
|
||||
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
|
||||
|
||||
if (s?.lineHeight) {
|
||||
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
|
||||
s.lineHeight = Math.round(s.fontSize * s.lineHeight)
|
||||
}
|
||||
} else if (!isNative) {
|
||||
s.lineHeight = s.fontSize
|
||||
}
|
||||
|
||||
applyFonts(s, fontFamily)
|
||||
|
||||
return s
|
||||
}
|
||||
export type {TextProps}
|
||||
|
||||
/**
|
||||
* Our main text component. Use this most of the time.
|
||||
@@ -162,10 +37,6 @@ export function Text({
|
||||
`Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
|
||||
)
|
||||
}
|
||||
|
||||
if (emoji && !childIsString(children)) {
|
||||
logger.error('Text: when <Text emoji />, children can only be strings.')
|
||||
}
|
||||
}
|
||||
|
||||
const shared = {
|
||||
@@ -178,12 +49,12 @@ export function Text({
|
||||
|
||||
return (
|
||||
<UITextView {...shared}>
|
||||
{isIOS && emoji ? renderChildrenWithEmoji(children, shared) : children}
|
||||
{renderChildrenWithEmoji(children, shared, emoji ?? false)}
|
||||
</UITextView>
|
||||
)
|
||||
}
|
||||
|
||||
export function createHeadingElement({level}: {level: number}) {
|
||||
function createHeadingElement({level}: {level: number}) {
|
||||
return function HeadingElement({style, ...rest}: TextProps) {
|
||||
const attr =
|
||||
web({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -256,6 +256,9 @@ export function PostInteractionSettingsForm({
|
||||
} else {
|
||||
newSelected.splice(i, 1)
|
||||
}
|
||||
if (newSelected.length === 0) {
|
||||
newSelected.push({type: 'everybody'})
|
||||
}
|
||||
|
||||
onChangeThreadgateAllowUISettings(newSelected)
|
||||
}
|
||||
@@ -306,7 +309,7 @@ export function PostInteractionSettingsForm({
|
||||
}
|
||||
value={quotesEnabled}
|
||||
onChange={onChangeQuotesEnabled}
|
||||
style={[, a.justify_between, a.pt_xs]}>
|
||||
style={[a.justify_between, a.pt_xs]}>
|
||||
<Text style={[t.atoms.text_contrast_medium]}>
|
||||
{quotesEnabled ? (
|
||||
<Trans>Quote posts enabled</Trans>
|
||||
@@ -483,7 +486,7 @@ function Selectable({
|
||||
a.justify_between,
|
||||
a.rounded_sm,
|
||||
a.p_md,
|
||||
{height: 40}, // for consistency with checkmark icon visible or not
|
||||
{minHeight: 40}, // for consistency with checkmark icon visible or not
|
||||
t.atoms.bg_contrast_50,
|
||||
(hovered || focused) && t.atoms.bg_contrast_100,
|
||||
isSelected && {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -115,7 +115,7 @@ let ConvoMenu = ({
|
||||
{...props}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss()
|
||||
// eslint-disable-next-line react/prop-types -- eslint is confused by the name `props`
|
||||
|
||||
props.onPress()
|
||||
}}
|
||||
style={[
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
@@ -19,10 +19,11 @@ import {ConvoItem} from '#/state/messages/convo/types'
|
||||
import {useSession} from '#/state/session'
|
||||
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {isOnlyEmoji} from '#/alf/typography'
|
||||
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {isOnlyEmoji, RichText} from '../RichText'
|
||||
import {RichText} from '../RichText'
|
||||
import {DateDivider} from './DateDivider'
|
||||
import {MessageItemEmbed} from './MessageItemEmbed'
|
||||
import {localDateString} from './util'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {useCallback} from 'react'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
@@ -16,17 +16,21 @@ import {
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
gl,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
pl,
|
||||
ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
zhCN,
|
||||
zhHK,
|
||||
zhTW,
|
||||
@@ -47,17 +51,21 @@ const locales: Record<AppLanguage, Locale | undefined> = {
|
||||
fi,
|
||||
fr,
|
||||
ga: undefined,
|
||||
gl,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
pl,
|
||||
['pt-BR']: ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
['zh-CN']: zhCN,
|
||||
['zh-HK']: zhHK,
|
||||
['zh-TW']: zhTW,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const ChainLink3_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M18.535 5.465a5.003 5.003 0 0 0-7.076 0l-.005.005-.752.742a1 1 0 1 1-1.404-1.424l.749-.74a7.003 7.003 0 0 1 9.904 9.905l-.002.003-.737.746a1 1 0 1 1-1.424-1.404l.747-.757a5.003 5.003 0 0 0 0-7.076ZM6.202 9.288a1 1 0 0 1 .01 1.414l-.747.757a5.003 5.003 0 1 0 7.076 7.076l.005-.005.752-.742a1 1 0 1 1 1.404 1.424l-.746.737-.003.002a7.003 7.003 0 0 1-9.904-9.904l.74-.75a1 1 0 0 1 1.413-.009Zm8.505.005a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414-1.414l4-4a1 1 0 0 1 1.414 0Z',
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TextProps} from 'react-native'
|
||||
import type {PathProps, SvgProps} from 'react-native-svg'
|
||||
import {Defs, LinearGradient, Stop} from 'react-native-svg'
|
||||
|
||||
@@ -31,6 +31,37 @@ export function ContentHider({
|
||||
ignoreMute?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
childContainerStyle?: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const blur = modui?.blurs[0]
|
||||
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ContentHiderActive
|
||||
testID={testID}
|
||||
modui={modui}
|
||||
style={style}
|
||||
childContainerStyle={childContainerStyle}>
|
||||
{children}
|
||||
</ContentHiderActive>
|
||||
)
|
||||
}
|
||||
|
||||
function ContentHiderActive({
|
||||
testID,
|
||||
modui,
|
||||
style,
|
||||
childContainerStyle,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
testID?: string
|
||||
modui: ModerationUI
|
||||
style?: StyleProp<ViewStyle>
|
||||
childContainerStyle?: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
@@ -40,7 +71,6 @@ export function ContentHider({
|
||||
const {labelDefs} = useLabelDefinitions()
|
||||
const globalLabelStrings = useGlobalLabelStrings()
|
||||
const {i18n} = useLingui()
|
||||
|
||||
const blur = modui?.blurs[0]
|
||||
const desc = useModerationCauseDescription(blur)
|
||||
|
||||
@@ -99,14 +129,6 @@ export function ContentHider({
|
||||
globalLabelStrings,
|
||||
])
|
||||
|
||||
if (!blur || (ignoreMute && isJustAMute(modui))) {
|
||||
return (
|
||||
<View testID={testID} style={style}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View testID={testID} style={[a.overflow_hidden, style]}>
|
||||
<ModerationDetailsDialog control={control} modcause={blur} />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api'
|
||||
import {msg, Plural} from '@lingui/macro'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {ModerationCause} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {ModerationCause, ModerationUI} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, ViewStyle} from 'react-native'
|
||||
import {ModerationDecision} from '@atproto/api'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
@@ -10,39 +9,23 @@ export function PlayButtonIcon({size = 32}: {size?: number}) {
|
||||
const fg = t.name === 'light' ? t.palette.contrast_975 : t.palette.contrast_25
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.overflow_hidden,
|
||||
a.align_center,
|
||||
a.justify_center,
|
||||
t.atoms.shadow_lg,
|
||||
{
|
||||
width: size + size / 1.5,
|
||||
height: size + size / 1.5,
|
||||
},
|
||||
]}>
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.rounded_full,
|
||||
{
|
||||
backgroundColor: bg,
|
||||
shadowColor: 'black',
|
||||
shadowRadius: 32,
|
||||
shadowOpacity: 0.5,
|
||||
elevation: 24,
|
||||
width: size + size / 1.5,
|
||||
height: size + size / 1.5,
|
||||
opacity: 0.7,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<PlayIcon
|
||||
width={size}
|
||||
fill={fg}
|
||||
style={[
|
||||
a.relative,
|
||||
a.z_10,
|
||||
{
|
||||
left: size / 50,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
<PlayIcon width={size} fill={fg} style={a.absolute} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ export const SUPPORTED_MIME_TYPES = [
|
||||
'video/mpeg',
|
||||
'video/webm',
|
||||
'video/quicktime',
|
||||
'image/gif',
|
||||
] as const
|
||||
|
||||
export type SupportedMimeTypes = (typeof SUPPORTED_MIME_TYPES)[number]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import Animated, {
|
||||
Keyframe,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
|
||||
import Animated, {
|
||||
cancelAnimation,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {useState} from 'react'
|
||||
import {AnimatedRef, measure, MeasuredDimensions} from 'react-native-reanimated'
|
||||
|
||||
export type HandleRef = {
|
||||
(node: any): void
|
||||
current: null | number
|
||||
}
|
||||
|
||||
// This is a lighterweight alternative to `useAnimatedRef()` for imperative UI thread actions.
|
||||
// Render it like <View ref={ref} />, then pass `ref.current` to `measureHandle()` and such.
|
||||
export function useHandleRef(): HandleRef {
|
||||
return useState(() => {
|
||||
const ref = (node: any) => {
|
||||
if (node) {
|
||||
ref.current =
|
||||
node._nativeTag ??
|
||||
node.__nativeTag ??
|
||||
node.canonical?.nativeTag ??
|
||||
null
|
||||
} else {
|
||||
ref.current = null
|
||||
}
|
||||
}
|
||||
ref.current = null
|
||||
return ref
|
||||
})[0] as HandleRef
|
||||
}
|
||||
|
||||
// When using this version, you need to read ref.current on the JS thread, and pass it to UI.
|
||||
export function measureHandle(
|
||||
current: number | null,
|
||||
): MeasuredDimensions | null {
|
||||
'worklet'
|
||||
if (current !== null) {
|
||||
return measure((() => current) as AnimatedRef<any>)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import * as WebBrowser from 'expo-web-browser'
|
||||
|
||||
import {
|
||||
createBskyAppAbsoluteUrl,
|
||||
isBskyAppUrl,
|
||||
isBskyRSSUrl,
|
||||
isRelativeUrl,
|
||||
} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useInAppBrowser} from '#/state/preferences/in-app-browser'
|
||||
import {useOptOutOfUtm} from '#/state/preferences/opt-out-of-utm'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||
|
||||
@@ -18,6 +20,7 @@ export function useOpenLink() {
|
||||
const enabled = useInAppBrowser()
|
||||
const t = useTheme()
|
||||
const sheetWrapper = useSheetWrapper()
|
||||
const optOutOfUtm = useOptOutOfUtm()
|
||||
|
||||
const openLink = useCallback(
|
||||
async (url: string, override?: boolean) => {
|
||||
@@ -26,6 +29,9 @@ export function useOpenLink() {
|
||||
}
|
||||
|
||||
if (isNative && !url.startsWith('mailto:')) {
|
||||
if (!optOutOfUtm && !isBskyAppUrl(url) && url.startsWith('http')) {
|
||||
url = addUtmSource(url)
|
||||
}
|
||||
if (override === undefined && enabled === undefined) {
|
||||
openModal({
|
||||
name: 'in-app-browser-consent',
|
||||
@@ -47,8 +53,24 @@ export function useOpenLink() {
|
||||
}
|
||||
Linking.openURL(url)
|
||||
},
|
||||
[enabled, openModal, t, sheetWrapper],
|
||||
[enabled, openModal, t, sheetWrapper, optOutOfUtm],
|
||||
)
|
||||
|
||||
return openLink
|
||||
}
|
||||
|
||||
function addUtmSource(url: string): string {
|
||||
let parsedUrl
|
||||
try {
|
||||
parsedUrl = new URL(url)
|
||||
} catch (e) {
|
||||
return url
|
||||
}
|
||||
if (!parsedUrl.searchParams.has('utm_source')) {
|
||||
parsedUrl.searchParams.set('utm_source', 'bluesky')
|
||||
if (!parsedUrl.searchParams.has('utm_medium')) {
|
||||
parsedUrl.searchParams.set('utm_medium', 'social')
|
||||
}
|
||||
}
|
||||
return parsedUrl.toString()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextStyle, ViewStyle} from 'react-native'
|
||||
import Svg, {Ellipse, Line, Path, Rect} from 'react-native-svg'
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) {
|
||||
return 'mpeg'
|
||||
case 'video/quicktime':
|
||||
return 'mov'
|
||||
case 'image/gif':
|
||||
return 'gif'
|
||||
default:
|
||||
throw new Error(`Unsupported mime type: ${mimeType}`)
|
||||
}
|
||||
@@ -47,6 +49,8 @@ export function extToMime(ext: string) {
|
||||
return 'video/mpeg'
|
||||
case 'mov':
|
||||
return 'video/quicktime'
|
||||
case 'gif':
|
||||
return 'image/gif'
|
||||
default:
|
||||
throw new Error(`Unsupported file extension: ${ext}`)
|
||||
}
|
||||
|
||||
@@ -2,4 +2,3 @@ export type Gate =
|
||||
// Keep this alphabetic please.
|
||||
| 'debug_show_feedcontext' // DISABLED DUE TO EME
|
||||
| 'post_feed_lang_window' // DISABLED DUE TO EME
|
||||
| 'suggested_feeds_interstitial'
|
||||
|
||||
@@ -11,4 +11,5 @@ test('sanitizeAppLanguageSetting', () => {
|
||||
expect(sanitizeAppLanguageSetting('foo')).toBe(AppLanguage.en)
|
||||
expect(sanitizeAppLanguageSetting('en,foo')).toBe(AppLanguage.en)
|
||||
expect(sanitizeAppLanguageSetting('foo,en')).toBe(AppLanguage.en)
|
||||
expect(sanitizeAppLanguageSetting('vi')).toBe(AppLanguage.vi)
|
||||
})
|
||||
|
||||
@@ -13,6 +13,12 @@ type LocalWithLanguageCode = Locale & {
|
||||
*
|
||||
* {@link https://github.com/bluesky-social/social-app/pull/4461}
|
||||
* {@link https://xml.coverpages.org/iso639a.html}
|
||||
*
|
||||
* Convert Chinese language tags for Native.
|
||||
*
|
||||
* {@link https://datatracker.ietf.org/doc/html/rfc5646#appendix-A}
|
||||
* {@link https://developer.apple.com/documentation/packagedescription/languagetag}
|
||||
* {@link https://gist.github.com/amake/0ac7724681ac1c178c6f95a5b09f03ce#new-locales-vs-old-locales-chinese}
|
||||
*/
|
||||
export function getLocales() {
|
||||
const locales = defaultGetLocales?.() ?? []
|
||||
@@ -32,10 +38,25 @@ export function getLocales() {
|
||||
// yiddish
|
||||
locale.languageCode = 'yi'
|
||||
}
|
||||
|
||||
// @ts-ignore checked above
|
||||
output.push(locale)
|
||||
}
|
||||
|
||||
if (typeof locale.languageTag === 'string') {
|
||||
if (locale.languageTag.startsWith('zh-Hans')) {
|
||||
// Simplified Chinese to zh-CN
|
||||
locale.languageTag = 'zh-CN'
|
||||
}
|
||||
if (locale.languageTag.startsWith('zh-Hant')) {
|
||||
// Traditional Chinese to zh-TW
|
||||
locale.languageTag = 'zh-TW'
|
||||
}
|
||||
if (locale.languageTag.startsWith('yue')) {
|
||||
// Cantonese (Yue) to zh-HK
|
||||
locale.languageTag = 'zh-HK'
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore checked above
|
||||
output.push(locale)
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
@@ -101,7 +101,7 @@ export function getTranslatorLink(text: string, lang: string): string {
|
||||
/**
|
||||
* Returns a valid `appLanguage` value from an arbitrary string.
|
||||
*
|
||||
* Contenxt: post-refactor, we populated some user's `appLanguage` setting with
|
||||
* Context: post-refactor, we populated some user's `appLanguage` setting with
|
||||
* `postLanguage`, which can be a comma-separated list of values. This breaks
|
||||
* `appLanguage` handling in the app, so we introduced this util to parse out a
|
||||
* valid `appLanguage` from the pre-populated `postLanguage` values.
|
||||
@@ -133,6 +133,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
||||
return AppLanguage.fr
|
||||
case 'ga':
|
||||
return AppLanguage.ga
|
||||
case 'gl':
|
||||
return AppLanguage.gl
|
||||
case 'hi':
|
||||
return AppLanguage.hi
|
||||
case 'hu':
|
||||
@@ -145,6 +147,10 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
||||
return AppLanguage.ja
|
||||
case 'ko':
|
||||
return AppLanguage.ko
|
||||
case 'nl':
|
||||
return AppLanguage.nl
|
||||
case 'pl':
|
||||
return AppLanguage.pl
|
||||
case 'pt-BR':
|
||||
return AppLanguage.pt_BR
|
||||
case 'ru':
|
||||
@@ -155,6 +161,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
||||
return AppLanguage.tr
|
||||
case 'uk':
|
||||
return AppLanguage.uk
|
||||
case 'vi':
|
||||
return AppLanguage.vi
|
||||
case 'zh-CN':
|
||||
return AppLanguage.zh_CN
|
||||
case 'zh-HK':
|
||||
|
||||
+37
-1
@@ -19,17 +19,21 @@ import {messages as messagesEs} from '#/locale/locales/es/messages'
|
||||
import {messages as messagesFi} from '#/locale/locales/fi/messages'
|
||||
import {messages as messagesFr} from '#/locale/locales/fr/messages'
|
||||
import {messages as messagesGa} from '#/locale/locales/ga/messages'
|
||||
import {messages as messagesGl} from '#/locale/locales/gl/messages'
|
||||
import {messages as messagesHi} from '#/locale/locales/hi/messages'
|
||||
import {messages as messagesHu} from '#/locale/locales/hu/messages'
|
||||
import {messages as messagesId} from '#/locale/locales/id/messages'
|
||||
import {messages as messagesIt} from '#/locale/locales/it/messages'
|
||||
import {messages as messagesJa} from '#/locale/locales/ja/messages'
|
||||
import {messages as messagesKo} from '#/locale/locales/ko/messages'
|
||||
import {messages as messagesNl} from '#/locale/locales/nl/messages'
|
||||
import {messages as messagesPl} from '#/locale/locales/pl/messages'
|
||||
import {messages as messagesPt_BR} from '#/locale/locales/pt-BR/messages'
|
||||
import {messages as messagesRu} from '#/locale/locales/ru/messages'
|
||||
import {messages as messagesTh} from '#/locale/locales/th/messages'
|
||||
import {messages as messagesTr} from '#/locale/locales/tr/messages'
|
||||
import {messages as messagesUk} from '#/locale/locales/uk/messages'
|
||||
import {messages as messagesVi} from '#/locale/locales/vi/messages'
|
||||
import {messages as messagesZh_CN} from '#/locale/locales/zh-CN/messages'
|
||||
import {messages as messagesZh_HK} from '#/locale/locales/zh-HK/messages'
|
||||
import {messages as messagesZh_TW} from '#/locale/locales/zh-TW/messages'
|
||||
@@ -96,6 +100,14 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.gl: {
|
||||
i18n.loadAndActivate({locale, messages: messagesGl})
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/gl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/gl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.hi: {
|
||||
i18n.loadAndActivate({locale, messages: messagesHi})
|
||||
await Promise.all([
|
||||
@@ -144,6 +156,22 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.nl: {
|
||||
i18n.loadAndActivate({locale, messages: messagesNl})
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/nl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/nl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.pl: {
|
||||
i18n.loadAndActivate({locale, messages: messagesPl})
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/pl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/pl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.pt_BR: {
|
||||
i18n.loadAndActivate({locale, messages: messagesPt_BR})
|
||||
await Promise.all([
|
||||
@@ -184,6 +212,14 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.vi: {
|
||||
i18n.loadAndActivate({locale, messages: messagesVi})
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/vi'),
|
||||
import('@formatjs/intl-numberformat/locale-data/vi'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.zh_CN: {
|
||||
i18n.loadAndActivate({locale, messages: messagesZh_CN})
|
||||
await Promise.all([
|
||||
@@ -215,7 +251,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function useLocaleLanguage() {
|
||||
export function useLocaleLanguage() {
|
||||
const {appLanguage} = useLanguagePrefs()
|
||||
useEffect(() => {
|
||||
dynamicActivate(sanitizeAppLanguageSetting(appLanguage))
|
||||
|
||||
@@ -40,6 +40,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
mod = await import(`./locales/ga/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.gl: {
|
||||
mod = await import(`./locales/gl/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.hi: {
|
||||
mod = await import(`./locales/hi/messages`)
|
||||
break
|
||||
@@ -64,6 +68,14 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
mod = await import(`./locales/ko/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.nl: {
|
||||
mod = await import(`./locales/nl/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.pl: {
|
||||
mod = await import(`./locales/pl/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.pt_BR: {
|
||||
mod = await import(`./locales/pt-BR/messages`)
|
||||
break
|
||||
@@ -84,6 +96,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
mod = await import(`./locales/uk/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.vi: {
|
||||
mod = await import(`./locales/vi/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.zh_CN: {
|
||||
mod = await import(`./locales/zh-CN/messages`)
|
||||
break
|
||||
|
||||
+10
-2
@@ -13,17 +13,21 @@ export enum AppLanguage {
|
||||
fi = 'fi',
|
||||
fr = 'fr',
|
||||
ga = 'ga',
|
||||
gl = 'gl',
|
||||
hi = 'hi',
|
||||
hu = 'hu',
|
||||
id = 'id',
|
||||
it = 'it',
|
||||
ja = 'ja',
|
||||
ko = 'ko',
|
||||
nl = 'nl',
|
||||
pl = 'pl',
|
||||
pt_BR = 'pt-BR',
|
||||
ru = 'ru',
|
||||
th = 'th',
|
||||
tr = 'tr',
|
||||
uk = 'uk',
|
||||
vi = 'vi',
|
||||
zh_CN = 'zh-CN',
|
||||
zh_HK = 'zh-HK',
|
||||
zh_TW = 'zh-TW',
|
||||
@@ -43,17 +47,21 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
|
||||
{code2: AppLanguage.fi, name: 'Suomi – Finnish'},
|
||||
{code2: AppLanguage.fr, name: 'Français – French'},
|
||||
{code2: AppLanguage.ga, name: 'Gaeilge – Irish'},
|
||||
{code2: AppLanguage.gl, name: 'Galego - Galician'},
|
||||
{code2: AppLanguage.hi, name: 'हिंदी – Hindi'},
|
||||
{code2: AppLanguage.hu, name: 'magyar – Hungarian'},
|
||||
{code2: AppLanguage.id, name: 'Bahasa Indonesia – Indonesian'},
|
||||
{code2: AppLanguage.it, name: 'Italiano – Italian'},
|
||||
{code2: AppLanguage.ja, name: '日本語 – Japanese'},
|
||||
{code2: AppLanguage.ko, name: '한국어 – Korean'},
|
||||
{code2: AppLanguage.nl, name: 'Nederlands – Dutch'},
|
||||
{code2: AppLanguage.pl, name: 'Polski – Polish'},
|
||||
{code2: AppLanguage.pt_BR, name: 'Português (BR) – Portuguese (BR)'},
|
||||
{code2: AppLanguage.ru, name: 'Русский – Russian'},
|
||||
{code2: AppLanguage.th, name: 'ภาษาไทย – Thai'},
|
||||
{code2: AppLanguage.tr, name: 'Türkçe – Turkish'},
|
||||
{code2: AppLanguage.uk, name: 'Українська – Ukrainian'},
|
||||
{code2: AppLanguage.vi, name: 'Tiếng Việt – Vietnamese'},
|
||||
{code2: AppLanguage.zh_CN, name: '简体中文 – Simplified Chinese'},
|
||||
{code2: AppLanguage.zh_TW, name: '繁體中文 – Traditional Chinese'},
|
||||
{code2: AppLanguage.zh_HK, name: '粵文 – Cantonese'},
|
||||
@@ -467,8 +475,8 @@ export const LANGUAGES: Language[] = [
|
||||
{code3: 'roa', code2: '', name: 'Romance languages'},
|
||||
{code3: 'roh', code2: 'rm', name: 'Romansh'},
|
||||
{code3: 'rom', code2: '', name: 'Romany'},
|
||||
{code3: 'rum', code2: 'ro', name: 'Romanian; Moldavian; Moldovan'},
|
||||
{code3: 'ron', code2: 'ro', name: 'Romanian; Moldavian; Moldovan'},
|
||||
{code3: 'rum', code2: 'ro', name: 'Romanian'},
|
||||
{code3: 'ron', code2: 'ro', name: 'Romanian'},
|
||||
{code3: 'run', code2: 'rn', name: 'Rundi'},
|
||||
{code3: 'rup', code2: '', name: 'Aromanian; Arumanian; Macedo-Romanian'},
|
||||
{code3: 'rus', code2: 'ru', name: 'Russian'},
|
||||
|
||||
+781
-731
File diff suppressed because it is too large
Load Diff
+653
-603
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+653
-603
File diff suppressed because it is too large
Load Diff
+860
-810
File diff suppressed because it is too large
Load Diff
+653
-603
File diff suppressed because it is too large
Load Diff
+830
-780
File diff suppressed because it is too large
Load Diff
+797
-747
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+920
-2193
File diff suppressed because it is too large
Load Diff
+785
-871
File diff suppressed because it is too large
Load Diff
+654
-604
File diff suppressed because it is too large
Load Diff
+978
-2702
File diff suppressed because it is too large
Load Diff
+654
-604
File diff suppressed because it is too large
Load Diff
+692
-670
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+654
-604
File diff suppressed because it is too large
Load Diff
+654
-604
File diff suppressed because it is too large
Load Diff
+653
-603
File diff suppressed because it is too large
Load Diff
+653
-603
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react'
|
||||
import {useState} from 'react'
|
||||
import {ActivityIndicator, View} from 'react-native'
|
||||
import {BskyAgent} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user