Merge branch 'main' into ja-translation-17
This commit is contained in:
@@ -33,6 +33,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
'bsky-internal/use-exact-imports': 'error',
|
'bsky-internal/use-exact-imports': 'error',
|
||||||
'bsky-internal/use-typed-gates': 'error',
|
'bsky-internal/use-typed-gates': 'error',
|
||||||
|
'bsky-internal/use-prefixed-imports': 'warn',
|
||||||
'simple-import-sort/imports': [
|
'simple-import-sort/imports': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ export function Embed({
|
|||||||
return <Info>The quoted post is blocked.</Info>
|
return <Info>The quoted post is blocked.</Info>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case 3.8: Detached quote post
|
||||||
|
if (AppBskyEmbedRecord.isViewDetached(record)) {
|
||||||
|
// Just don't show anything
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Unknown embed type
|
// Unknown embed type
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,22 @@
|
|||||||
|
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Regular.1f5ed03b6dd9fd1f9982.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Regular.1f5ed03b6dd9fd1f9982.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Italic.95778eb0c75dc956257e.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Italic.95778eb0c75dc956257e.otf">
|
||||||
|
<!--
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Medium.296aa2d65964269836b3.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Medium.296aa2d65964269836b3.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-MediumItalic.0e57e17a6311368e2114.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-MediumItalic.0e57e17a6311368e2114.otf">
|
||||||
|
-->
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-SemiBold.2277990330981b8409bb.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-SemiBold.2277990330981b8409bb.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-SemiBoldItalic.f62fea3df3a521d6c8a7.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-SemiBoldItalic.f62fea3df3a521d6c8a7.otf">
|
||||||
|
<!--
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Bold.8d330503e1d034ad68de.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Bold.8d330503e1d034ad68de.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-BoldItalic.bb17e63f9baa0d861a20.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-BoldItalic.bb17e63f9baa0d861a20.otf">
|
||||||
|
-->
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-ExtraBoldItalic.0e50b40728d24d40fdf4.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-ExtraBoldItalic.0e50b40728d24d40fdf4.otf">
|
||||||
|
<!--
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Black.66e9a87f1c921e844ed4.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-Black.66e9a87f1c921e844ed4.otf">
|
||||||
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-BlackItalic.27b9f0ad06fd13a7b9da.otf">
|
<link rel="preload" as="font" type="font/otf" href="/static/media/Inter-BlackItalic.27b9f0ad06fd13a7b9da.otf">
|
||||||
|
-->
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -41,6 +47,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter-Medium";
|
font-family: "Inter-Medium";
|
||||||
src: local("Inter-Medium"), url(/static/media/Inter-Medium.296aa2d65964269836b3.otf) format("font/otf");
|
src: local("Inter-Medium"), url(/static/media/Inter-Medium.296aa2d65964269836b3.otf) format("font/otf");
|
||||||
@@ -55,6 +62,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter-SemiBold";
|
font-family: "Inter-SemiBold";
|
||||||
src: local("Inter-SemiBold"), url(/static/media/Inter-SemiBold.2277990330981b8409bb.otf) format("font/otf");
|
src: local("Inter-SemiBold"), url(/static/media/Inter-SemiBold.2277990330981b8409bb.otf) format("font/otf");
|
||||||
@@ -69,6 +77,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter-Bold";
|
font-family: "Inter-Bold";
|
||||||
src: local("Inter-Bold"), url(/static/media/Inter-Bold.8d330503e1d034ad68de.otf) format("font/otf");
|
src: local("Inter-Bold"), url(/static/media/Inter-Bold.8d330503e1d034ad68de.otf) format("font/otf");
|
||||||
@@ -83,6 +92,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter-ExtraBold";
|
font-family: "Inter-ExtraBold";
|
||||||
src: local("Inter-ExtraBold"), url(/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf) format("font/otf");
|
src: local("Inter-ExtraBold"), url(/static/media/Inter-ExtraBold.ff2581a193bf6b7e0b06.otf) format("font/otf");
|
||||||
@@ -97,6 +107,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter-Black";
|
font-family: "Inter-Black";
|
||||||
src: local("Inter-Black"), url(/static/media/Inter-Black.66e9a87f1c921e844ed4.otf) format("font/otf");
|
src: local("Inter-Black"), url(/static/media/Inter-Black.66e9a87f1c921e844ed4.otf) format("font/otf");
|
||||||
@@ -111,6 +122,7 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the react-native-web reset:
|
* Extend the react-native-web reset:
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ module.exports = {
|
|||||||
'avoid-unwrapped-text': require('./avoid-unwrapped-text'),
|
'avoid-unwrapped-text': require('./avoid-unwrapped-text'),
|
||||||
'use-exact-imports': require('./use-exact-imports'),
|
'use-exact-imports': require('./use-exact-imports'),
|
||||||
'use-typed-gates': require('./use-typed-gates'),
|
'use-typed-gates': require('./use-typed-gates'),
|
||||||
|
'use-prefixed-imports': require('./use-prefixed-imports'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable bsky-internal/use-exact-imports */
|
|
||||||
const BANNED_IMPORTS = [
|
const BANNED_IMPORTS = [
|
||||||
'@fortawesome/free-regular-svg-icons',
|
'@fortawesome/free-regular-svg-icons',
|
||||||
'@fortawesome/free-solid-svg-icons',
|
'@fortawesome/free-solid-svg-icons',
|
||||||
@@ -6,11 +5,12 @@ const BANNED_IMPORTS = [
|
|||||||
|
|
||||||
exports.create = function create(context) {
|
exports.create = function create(context) {
|
||||||
return {
|
return {
|
||||||
Literal(node) {
|
ImportDeclaration(node) {
|
||||||
if (typeof node.value !== 'string') {
|
const source = node.source
|
||||||
|
if (typeof source.value !== 'string') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (BANNED_IMPORTS.includes(node.value)) {
|
if (BANNED_IMPORTS.includes(source.value)) {
|
||||||
context.report({
|
context.report({
|
||||||
node,
|
node,
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -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}'`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"await-lock": "^2.2.2",
|
"await-lock": "^2.2.2",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"bcp-47": "^2.1.0",
|
||||||
"bcp-47-match": "^2.0.3",
|
"bcp-47-match": "^2.0.3",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"deprecated-react-native-prop-types": "^5.0.0",
|
"deprecated-react-native-prop-types": "^5.0.0",
|
||||||
@@ -204,6 +205,7 @@
|
|||||||
"statsig-react-native-expo": "^4.6.1",
|
"statsig-react-native-expo": "^4.6.1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tlds": "^1.234.0",
|
"tlds": "^1.234.0",
|
||||||
|
"tldts": "^6.1.46",
|
||||||
"zeego": "^1.6.2",
|
"zeego": "^1.6.2",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,3 +12,15 @@ index bb74e80..0aa0202 100644
|
|||||||
|
|
||||||
Map<String, Object> constants = new HashMap<>(3);
|
Map<String, Object> constants = new HashMap<>(3);
|
||||||
constants.put(MODULES_CONSTANTS_KEY, new HashMap<>());
|
constants.put(MODULES_CONSTANTS_KEY, new HashMap<>());
|
||||||
|
diff --git a/node_modules/expo-modules-core/build/uuid/uuid.js b/node_modules/expo-modules-core/build/uuid/uuid.js
|
||||||
|
index 109d3fe..c7fce9e 100644
|
||||||
|
--- a/node_modules/expo-modules-core/build/uuid/uuid.js
|
||||||
|
+++ b/node_modules/expo-modules-core/build/uuid/uuid.js
|
||||||
|
@@ -1,5 +1,7 @@
|
||||||
|
import bytesToUuid from './lib/bytesToUuid';
|
||||||
|
import { Uuidv5Namespace } from './uuid.types';
|
||||||
|
+import { ensureNativeModulesAreInstalled } from '../ensureNativeModulesAreInstalled';
|
||||||
|
+ensureNativeModulesAreInstalled();
|
||||||
|
const nativeUuidv4 = globalThis?.expo?.uuidv4;
|
||||||
|
const nativeUuidv5 = globalThis?.expo?.uuidv5;
|
||||||
|
function uuidv4() {
|
||||||
|
|||||||
+15
-1
@@ -29,6 +29,11 @@ import {Provider as A11yProvider} from '#/state/a11y'
|
|||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
import {listenSessionDropped} from '#/state/events'
|
import {listenSessionDropped} from '#/state/events'
|
||||||
|
import {
|
||||||
|
beginResolveGeolocation,
|
||||||
|
ensureGeolocationResolved,
|
||||||
|
Provider as GeolocationProvider,
|
||||||
|
} from '#/state/geolocation'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
import {Provider as InvitesStateProvider} from '#/state/invites'
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
@@ -66,6 +71,11 @@ import {BackgroundNotificationPreferencesProvider} from '../modules/expo-backgro
|
|||||||
|
|
||||||
SplashScreen.preventAutoHideAsync()
|
SplashScreen.preventAutoHideAsync()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin geolocation ASAP
|
||||||
|
*/
|
||||||
|
beginResolveGeolocation()
|
||||||
|
|
||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const [isReady, setIsReady] = React.useState(false)
|
const [isReady, setIsReady] = React.useState(false)
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
@@ -158,7 +168,9 @@ function App() {
|
|||||||
const [isReady, setReady] = useState(false)
|
const [isReady, setReady] = useState(false)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initPersistedState().then(() => setReady(true))
|
Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() =>
|
||||||
|
setReady(true),
|
||||||
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
@@ -170,6 +182,7 @@ function App() {
|
|||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
|
<GeolocationProvider>
|
||||||
<A11yProvider>
|
<A11yProvider>
|
||||||
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
|
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
@@ -200,6 +213,7 @@ function App() {
|
|||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</KeyboardProvider>
|
</KeyboardProvider>
|
||||||
</A11yProvider>
|
</A11yProvider>
|
||||||
|
</GeolocationProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -18,6 +18,11 @@ import {Provider as A11yProvider} from '#/state/a11y'
|
|||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
import {listenSessionDropped} from '#/state/events'
|
import {listenSessionDropped} from '#/state/events'
|
||||||
|
import {
|
||||||
|
beginResolveGeolocation,
|
||||||
|
ensureGeolocationResolved,
|
||||||
|
Provider as GeolocationProvider,
|
||||||
|
} from '#/state/geolocation'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
import {Provider as InvitesStateProvider} from '#/state/invites'
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
@@ -54,6 +59,11 @@ import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialo
|
|||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin geolocation ASAP
|
||||||
|
*/
|
||||||
|
beginResolveGeolocation()
|
||||||
|
|
||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const [isReady, setIsReady] = React.useState(false)
|
const [isReady, setIsReady] = React.useState(false)
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
@@ -148,7 +158,9 @@ function App() {
|
|||||||
const [isReady, setReady] = useState(false)
|
const [isReady, setReady] = useState(false)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initPersistedState().then(() => setReady(true))
|
Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() =>
|
||||||
|
setReady(true),
|
||||||
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
@@ -160,6 +172,7 @@ function App() {
|
|||||||
* that is set up in the InnerApp component above.
|
* that is set up in the InnerApp component above.
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
|
<GeolocationProvider>
|
||||||
<A11yProvider>
|
<A11yProvider>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<PrefsStateProvider>
|
<PrefsStateProvider>
|
||||||
@@ -185,6 +198,7 @@ function App() {
|
|||||||
</PrefsStateProvider>
|
</PrefsStateProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</A11yProvider>
|
</A11yProvider>
|
||||||
|
</GeolocationProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -276,16 +276,13 @@ export const atoms = {
|
|||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
font_normal: {
|
font_normal: {
|
||||||
fontWeight: tokens.fontWeight.normal,
|
fontWeight: tokens.fontWeight.regular,
|
||||||
},
|
|
||||||
font_semibold: {
|
|
||||||
fontWeight: tokens.fontWeight.semibold,
|
|
||||||
},
|
},
|
||||||
font_bold: {
|
font_bold: {
|
||||||
fontWeight: tokens.fontWeight.bold,
|
fontWeight: tokens.fontWeight.semibold,
|
||||||
},
|
},
|
||||||
font_heavy: {
|
font_heavy: {
|
||||||
fontWeight: tokens.fontWeight.heavy,
|
fontWeight: tokens.fontWeight.extrabold,
|
||||||
},
|
},
|
||||||
italic: {
|
italic: {
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
|
|||||||
+6
-6
@@ -54,16 +54,16 @@ export function DO_NOT_USE() {
|
|||||||
// 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
|
// 'Inter-LightItalic': require('../../assets/fonts/inter/Inter-LightItalic.otf'),
|
||||||
'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
|
'Inter-Regular': require('../../assets/fonts/inter/Inter-Regular.otf'),
|
||||||
'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
|
'Inter-Italic': require('../../assets/fonts/inter/Inter-Italic.otf'),
|
||||||
'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
|
// 'Inter-Medium': require('../../assets/fonts/inter/Inter-Medium.otf'),
|
||||||
'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
|
// 'Inter-MediumItalic': require('../../assets/fonts/inter/Inter-MediumItalic.otf'),
|
||||||
'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
|
'Inter-SemiBold': require('../../assets/fonts/inter/Inter-SemiBold.otf'),
|
||||||
'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
|
'Inter-SemiBoldItalic': require('../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
|
||||||
'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
|
// 'Inter-Bold': require('../../assets/fonts/inter/Inter-Bold.otf'),
|
||||||
'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
|
// 'Inter-BoldItalic': require('../../assets/fonts/inter/Inter-BoldItalic.otf'),
|
||||||
'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
|
'Inter-ExtraBold': require('../../assets/fonts/inter/Inter-ExtraBold.otf'),
|
||||||
'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
|
'Inter-ExtraBoldItalic': require('../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
|
||||||
'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
|
// 'Inter-Black': require('../../assets/fonts/inter/Inter-Black.otf'),
|
||||||
'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
|
// 'Inter-BlackItalic': require('../../assets/fonts/inter/Inter-BlackItalic.otf'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-4
@@ -47,11 +47,16 @@ export const borderRadius = {
|
|||||||
full: 999,
|
full: 999,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These correspond to Inter font files we actually load.
|
||||||
|
*/
|
||||||
export const fontWeight = {
|
export const fontWeight = {
|
||||||
normal: '400',
|
regular: '400',
|
||||||
semibold: '500',
|
// medium: '500',
|
||||||
bold: '600',
|
semibold: '600',
|
||||||
heavy: '700',
|
// bold: '700',
|
||||||
|
extrabold: '800',
|
||||||
|
// black: '900',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const gradients = {
|
export const gradients = {
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export function AppLanguageDropdown() {
|
|||||||
if (sanitizedLang !== value) {
|
if (sanitizedLang !== value) {
|
||||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||||
}
|
}
|
||||||
setLangPrefs.setPrimaryLanguage(value)
|
|
||||||
setLangPrefs.setContentLanguage(value)
|
|
||||||
|
|
||||||
// reset feeds to refetch content
|
// reset feeds to refetch content
|
||||||
resetPostsFeedQueries(queryClient)
|
resetPostsFeedQueries(queryClient)
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ export function AppLanguageDropdown() {
|
|||||||
if (sanitizedLang !== value) {
|
if (sanitizedLang !== value) {
|
||||||
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
|
||||||
}
|
}
|
||||||
setLangPrefs.setPrimaryLanguage(value)
|
|
||||||
setLangPrefs.setContentLanguage(value)
|
|
||||||
|
|
||||||
// reset feeds to refetch content
|
// reset feeds to refetch content
|
||||||
resetPostsFeedQueries(queryClient)
|
resetPostsFeedQueries(queryClient)
|
||||||
|
|||||||
+108
-34
@@ -14,7 +14,7 @@ import {
|
|||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
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 {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export type ButtonColor =
|
|||||||
| 'gradient_sunset'
|
| 'gradient_sunset'
|
||||||
| 'gradient_nordic'
|
| 'gradient_nordic'
|
||||||
| 'gradient_bonfire'
|
| 'gradient_bonfire'
|
||||||
export type ButtonSize = 'tiny' | 'xsmall' | 'small' | 'medium' | 'large'
|
export type ButtonSize = 'tiny' | 'small' | 'large'
|
||||||
export type ButtonShape = 'round' | 'square' | 'default'
|
export type ButtonShape = 'round' | 'square' | 'default'
|
||||||
export type VariantProps = {
|
export type VariantProps = {
|
||||||
/**
|
/**
|
||||||
@@ -343,39 +343,46 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
|||||||
|
|
||||||
if (shape === 'default') {
|
if (shape === 'default') {
|
||||||
if (size === 'large') {
|
if (size === 'large') {
|
||||||
baseStyles.push(
|
baseStyles.push({
|
||||||
{paddingVertical: 15},
|
paddingVertical: 13,
|
||||||
a.px_2xl,
|
paddingHorizontal: 20,
|
||||||
a.rounded_sm,
|
borderRadius: 8,
|
||||||
a.gap_md,
|
gap: 8,
|
||||||
)
|
})
|
||||||
} else if (size === 'medium') {
|
|
||||||
baseStyles.push(
|
|
||||||
{paddingVertical: 12},
|
|
||||||
a.px_2xl,
|
|
||||||
a.rounded_sm,
|
|
||||||
a.gap_md,
|
|
||||||
)
|
|
||||||
} else if (size === 'small') {
|
} else if (size === 'small') {
|
||||||
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
|
baseStyles.push({
|
||||||
} else if (size === 'xsmall') {
|
paddingVertical: 8,
|
||||||
baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm)
|
paddingHorizontal: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
gap: 6,
|
||||||
|
})
|
||||||
} else if (size === 'tiny') {
|
} 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') {
|
} else if (shape === 'round' || shape === 'square') {
|
||||||
if (size === 'large') {
|
if (size === 'large') {
|
||||||
if (shape === 'round') {
|
if (shape === 'round') {
|
||||||
baseStyles.push({height: 54, width: 54})
|
baseStyles.push({height: 46, width: 46})
|
||||||
} else {
|
} else {
|
||||||
baseStyles.push({height: 50, width: 50})
|
baseStyles.push({height: 44, width: 44})
|
||||||
}
|
}
|
||||||
} else if (size === 'small') {
|
} else if (size === 'small') {
|
||||||
|
if (shape === 'round') {
|
||||||
|
baseStyles.push({height: 36, width: 36})
|
||||||
|
} else {
|
||||||
baseStyles.push({height: 34, width: 34})
|
baseStyles.push({height: 34, width: 34})
|
||||||
} else if (size === 'xsmall') {
|
}
|
||||||
baseStyles.push({height: 28, width: 28})
|
|
||||||
} else if (size === 'tiny') {
|
} 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') {
|
if (shape === 'round') {
|
||||||
@@ -619,11 +626,11 @@ export function useSharedButtonTextStyles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (size === 'large') {
|
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') {
|
} else if (size === 'tiny') {
|
||||||
baseStyles.push(a.text_xs, android({paddingBottom: 1}))
|
baseStyles.push(a.text_xs, a.leading_tight)
|
||||||
} else {
|
|
||||||
baseStyles.push(a.text_sm, android({paddingBottom: 1}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return StyleSheet.flatten(baseStyles)
|
return StyleSheet.flatten(baseStyles)
|
||||||
@@ -643,31 +650,98 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
|
|||||||
export function ButtonIcon({
|
export function ButtonIcon({
|
||||||
icon: Comp,
|
icon: Comp,
|
||||||
position,
|
position,
|
||||||
size: iconSize,
|
size,
|
||||||
}: {
|
}: {
|
||||||
icon: React.ComponentType<SVGIconProps>
|
icon: React.ComponentType<SVGIconProps>
|
||||||
position?: 'left' | 'right'
|
position?: 'left' | 'right'
|
||||||
size?: SVGIconProps['size']
|
size?: SVGIconProps['size']
|
||||||
}) {
|
}) {
|
||||||
const {size, disabled} = useButtonContext()
|
const {size: buttonSize, disabled} = useButtonContext()
|
||||||
const textStyles = useSharedButtonTextStyles()
|
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 (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.z_20,
|
a.z_20,
|
||||||
{
|
{
|
||||||
|
width: iconContainerSize,
|
||||||
|
height: iconContainerSize,
|
||||||
opacity: disabled ? 0.7 : 1,
|
opacity: disabled ? 0.7 : 1,
|
||||||
marginLeft: position === 'left' ? -2 : 0,
|
marginLeft: position === 'left' ? -2 : 0,
|
||||||
marginRight: position === 'right' ? -2 : 0,
|
marginRight: position === 'right' ? -2 : 0,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.absolute,
|
||||||
|
{
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: (iconSize / 2) * -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
translateY: (iconSize / 2) * -1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}>
|
||||||
<Comp
|
<Comp
|
||||||
size={
|
width={iconSize}
|
||||||
iconSize ?? (size === 'large' ? 'md' : size === 'tiny' ? 'xs' : 'sm')
|
style={[
|
||||||
}
|
{
|
||||||
style={[{color: textStyles.color, pointerEvents: 'none'}]}
|
color: textStyles.color,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {sanitizeHandle} from '#/lib/strings/handles'
|
|||||||
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
import {useLabelerInfoQuery} from '#/state/queries/labeler'
|
||||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
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 {Link as InternalLink, LinkProps} from '#/components/Link'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -43,21 +44,40 @@ export function Avatar({avatar}: {avatar?: string}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Title({value}: {value: string}) {
|
export function Title({value}: {value: string}) {
|
||||||
return <Text style={[a.text_md, a.font_bold]}>{value}</Text>
|
return <Text style={[a.text_md, a.font_bold, a.leading_tight]}>{value}</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Description({value, handle}: {value?: string; handle: string}) {
|
export function Description({value, handle}: {value?: string; handle: string}) {
|
||||||
return value ? (
|
return value ? (
|
||||||
<Text numberOfLines={2}>
|
<Text numberOfLines={2}>
|
||||||
<RichText value={value} style={[]} />
|
<RichText value={value} style={[a.leading_snug]} />
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text>
|
<Text style={[a.leading_snug]}>
|
||||||
<Trans>By {sanitizeHandle(handle, '@')}</Trans>
|
<Trans>By {sanitizeHandle(handle, '@')}</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function RegionalNotice() {
|
||||||
|
const t = useTheme()
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_xs,
|
||||||
|
a.pt_2xs,
|
||||||
|
{marginLeft: -2},
|
||||||
|
]}>
|
||||||
|
<Flag fill={t.atoms.text_contrast_low.color} size="sm" />
|
||||||
|
<Text style={[a.italic, a.leading_snug]}>
|
||||||
|
<Trans>Required in your region</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function LikeCount({count}: {count: number}) {
|
export function LikeCount({count}: {count: number}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
return (
|
return (
|
||||||
@@ -66,7 +86,7 @@ export function LikeCount({count}: {count: number}) {
|
|||||||
a.mt_sm,
|
a.mt_sm,
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
{fontWeight: '500'},
|
{fontWeight: '600'},
|
||||||
]}>
|
]}>
|
||||||
<Plural value={count} one="Liked by # user" other="Liked by # users" />
|
<Plural value={count} one="Liked by # user" other="Liked by # users" />
|
||||||
</Text>
|
</Text>
|
||||||
@@ -85,7 +105,7 @@ export function Content({children}: React.PropsWithChildren<{}>) {
|
|||||||
a.align_center,
|
a.align_center,
|
||||||
a.justify_between,
|
a.justify_between,
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.gap_xs, a.flex_1]}>{children}</View>
|
<View style={[a.gap_2xs, a.flex_1]}>{children}</View>
|
||||||
|
|
||||||
<ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
|
<ChevronRight size="md" style={[a.z_10, t.atoms.text_contrast_low]} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function MediaInsetBorder({
|
|||||||
return (
|
return (
|
||||||
<Fill
|
<Fill
|
||||||
style={[
|
style={[
|
||||||
a.rounded_sm,
|
a.rounded_md,
|
||||||
a.border,
|
a.border,
|
||||||
opaque
|
opaque
|
||||||
? [t.atoms.border_contrast_low]
|
? [t.atoms.border_contrast_low]
|
||||||
|
|||||||
@@ -170,6 +170,6 @@ const styles = StyleSheet.create({
|
|||||||
alt: {
|
alt: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
fontSize: 7,
|
fontSize: 7,
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -179,8 +179,10 @@ export function Outer({
|
|||||||
style={[
|
style={[
|
||||||
a.rounded_sm,
|
a.rounded_sm,
|
||||||
a.p_xs,
|
a.p_xs,
|
||||||
|
a.border,
|
||||||
t.name === 'light' ? t.atoms.bg : t.atoms.bg_contrast_25,
|
t.name === 'light' ? t.atoms.bg : t.atoms.bg_contrast_25,
|
||||||
t.atoms.shadow_md,
|
t.atoms.shadow_md,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export function Label({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
text,
|
text,
|
||||||
a.font_semibold,
|
a.font_bold,
|
||||||
a.leading_tight,
|
a.leading_tight,
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
{paddingRight: 3},
|
{paddingRight: 3},
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ function Inner({
|
|||||||
() => currentAccount?.did === profile.did,
|
() => currentAccount?.did === profile.did,
|
||||||
[currentAccount, profile],
|
[currentAccount, profile],
|
||||||
)
|
)
|
||||||
|
const isLabeler = profile.associated?.labeler
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
@@ -419,11 +420,13 @@ function Inner({
|
|||||||
<UserAvatar
|
<UserAvatar
|
||||||
size={64}
|
size={64}
|
||||||
avatar={profile.avatar}
|
avatar={profile.avatar}
|
||||||
|
type={isLabeler ? 'labeler' : 'user'}
|
||||||
moderation={moderation.ui('avatar')}
|
moderation={moderation.ui('avatar')}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{!isMe &&
|
{!isMe &&
|
||||||
|
!isLabeler &&
|
||||||
(isBlockedUser ? (
|
(isBlockedUser ? (
|
||||||
<Link
|
<Link
|
||||||
to={profileURL}
|
to={profileURL}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function ProgressGuideList({style}: {style?: StyleProp<ViewStyle>}) {
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
a.font_semibold,
|
a.font_bold,
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
{textTransform: 'uppercase'},
|
{textTransform: 'uppercase'},
|
||||||
]}>
|
]}>
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ export function ProgressGuideTask({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={[a.flex_col, a.gap_2xs, {marginTop: -2}]}>
|
<View style={[a.flex_col, a.gap_2xs, {marginTop: -2}]}>
|
||||||
<Text style={[a.text_sm, a.font_semibold, a.leading_tight]}>
|
<Text style={[a.text_sm, a.font_bold, a.leading_tight]}>{title}</Text>
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<Text
|
<Text
|
||||||
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}>
|
style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_tight]}>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export const ProgressGuideToast = React.forwardRef<
|
|||||||
ref={animatedCheckRef}
|
ref={animatedCheckRef}
|
||||||
/>
|
/>
|
||||||
<View>
|
<View>
|
||||||
<Text style={[a.text_md, a.font_semibold]}>{title}</Text>
|
<Text style={[a.text_md, a.font_bold]}>{title}</Text>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
<Text style={[a.text_sm, t.atoms.text_contrast_medium]}>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export function Cancel({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size={gtMobile ? 'small' : 'medium'}
|
size={gtMobile ? 'small' : 'large'}
|
||||||
label={cta || _(msg`Cancel`)}
|
label={cta || _(msg`Cancel`)}
|
||||||
onPress={onPress}>
|
onPress={onPress}>
|
||||||
<ButtonText>{cta || _(msg`Cancel`)}</ButtonText>
|
<ButtonText>{cta || _(msg`Cancel`)}</ButtonText>
|
||||||
@@ -163,7 +163,7 @@ export function Action({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color={color}
|
color={color}
|
||||||
size={gtMobile ? 'small' : 'medium'}
|
size={gtMobile ? 'small' : 'large'}
|
||||||
label={cta || _(msg`Confirm`)}
|
label={cta || _(msg`Confirm`)}
|
||||||
onPress={handleOnPress}
|
onPress={handleOnPress}
|
||||||
testID={testID}>
|
testID={testID}>
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ function LabelerButton({
|
|||||||
handle: labeler.creator.handle,
|
handle: labeler.creator.handle,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text style={[t.atoms.text_contrast_medium, a.text_sm, a.font_bold]}>
|
||||||
style={[t.atoms.text_contrast_medium, a.text_sm, a.font_semibold]}>
|
|
||||||
@{labeler.creator.handle}
|
@{labeler.creator.handle}
|
||||||
</Text>
|
</Text>
|
||||||
</LabelingServiceCard.Content>
|
</LabelingServiceCard.Content>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function WizardEditListDialog({
|
|||||||
label={_(msg`Close`)}
|
label={_(msg`Close`)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={() => control.close()}>
|
onPress={() => control.close()}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Close</Trans>
|
<Trans>Close</Trans>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ function WizardListCard({
|
|||||||
label={_(msg`Remove`)}
|
label={_(msg`Remove`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
style={[a.self_center, {marginLeft: 'auto'}]}
|
style={[a.self_center, {marginLeft: 'auto'}]}
|
||||||
onPress={onPress}>
|
onPress={onPress}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ function BirthdayInner({
|
|||||||
<View style={isWeb && [a.flex_row, a.justify_end]}>
|
<View style={isWeb && [a.flex_row, a.justify_end]}>
|
||||||
<Button
|
<Button
|
||||||
label={hasChanged ? _(msg`Save birthday`) : _(msg`Done`)}
|
label={hasChanged ? _(msg`Save birthday`) : _(msg`Done`)}
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onSave}
|
onPress={onSave}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary">
|
color="primary">
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ function EmbedDialogInner({
|
|||||||
label={_(msg`Copy code`)}
|
label={_(msg`Copy code`)}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
ref.current?.focus()
|
ref.current?.focus()
|
||||||
ref.current?.setSelection(0, snippet.length)
|
ref.current?.setSelection(0, snippet.length)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function EmbedConsentDialog({
|
|||||||
onPress={onShowAllPress}
|
onPress={onShowAllPress}
|
||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid">
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Enable external media</Trans>
|
<Trans>Enable external media</Trans>
|
||||||
@@ -95,7 +95,7 @@ export function EmbedConsentDialog({
|
|||||||
onPress={onShowPress}
|
onPress={onShowPress}
|
||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid">
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
<Trans>Enable {externalEmbedLabels[source]} only</Trans>
|
||||||
@@ -106,7 +106,7 @@ export function EmbedConsentDialog({
|
|||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
onPress={onHidePress}
|
onPress={onHidePress}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="ghost">
|
variant="ghost">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>No thanks</Trans>
|
<Trans>No thanks</Trans>
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ function ModalError({details, close}: {details?: string; close: () => void}) {
|
|||||||
label={_(msg`Close dialog`)}
|
label={_(msg`Close dialog`)}
|
||||||
onPress={close}
|
onPress={close}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid">
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Close</Trans>
|
<Trans>Close</Trans>
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ function DialogError({details}: {details?: string}) {
|
|||||||
label={_(msg`Close dialog`)}
|
label={_(msg`Close dialog`)}
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid">
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Close</Trans>
|
<Trans>Close</Trans>
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ function MutedWordsInner() {
|
|||||||
<Button
|
<Button
|
||||||
disabled={isPending || !field}
|
disabled={isPending || !field}
|
||||||
label={_(msg`Add mute word for configured settings`)}
|
label={_(msg`Add mute word for configured settings`)}
|
||||||
size="medium"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
style={[]}
|
style={[]}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ export function PostInteractionSettingsForm({
|
|||||||
onPress={onSave}
|
onPress={onSave}
|
||||||
onAccessibilityEscape={control.close}
|
onAccessibilityEscape={control.close}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
style={a.mt_xl}>
|
style={a.mt_xl}>
|
||||||
<ButtonText>{_(msg`Save`)}</ButtonText>
|
<ButtonText>{_(msg`Save`)}</ButtonText>
|
||||||
@@ -491,9 +491,7 @@ function Selectable({
|
|||||||
},
|
},
|
||||||
style,
|
style,
|
||||||
]}>
|
]}>
|
||||||
<Text style={[a.text_sm, isSelected && a.font_semibold]}>
|
<Text style={[a.text_sm, isSelected && a.font_bold]}>{label}</Text>
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
<Check size="sm" fill={t.palette.primary_500} />
|
<Check size="sm" fill={t.palette.primary_500} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function NeueTypography() {
|
|||||||
<Dialog.ScrollableInner label={_(msg`Introducing new font settings`)}>
|
<Dialog.ScrollableInner label={_(msg`Introducing new font settings`)}>
|
||||||
<View style={[a.gap_xl]}>
|
<View style={[a.gap_xl]}>
|
||||||
<View style={[a.gap_md]}>
|
<View style={[a.gap_md]}>
|
||||||
<Text style={[a.text_3xl, {fontWeight: '900'}]}>
|
<Text style={[a.text_3xl, a.font_heavy]}>
|
||||||
<Trans>New font settings ✨</Trans>
|
<Trans>New font settings ✨</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[a.text_lg, a.leading_snug, {maxWidth: 400}]}>
|
<Text style={[a.text_lg, a.leading_snug, {maxWidth: 400}]}>
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import {isIOS, isNative} from '#/platform/detection'
|
|||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useAgent, useSession} from '#/state/session'
|
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 {formatCount} from '#/view/com/util/numeric/format'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {Logomark} from '#/view/icons/Logomark'
|
import {Logomark} from '#/view/icons/Logomark'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
|
||||||
import {
|
import {
|
||||||
atoms as a,
|
atoms as a,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
@@ -441,10 +441,10 @@ export function TenMillionInner({
|
|||||||
allowFontScaling={false}
|
allowFontScaling={false}
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
|
a.font_heavy,
|
||||||
{
|
{
|
||||||
color: t.palette.primary_500,
|
color: t.palette.primary_500,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
fontWeight: '900',
|
|
||||||
width: 32,
|
width: 32,
|
||||||
top: isNative ? -10 : 0,
|
top: isNative ? -10 : 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -462,11 +462,11 @@ export function TenMillionInner({
|
|||||||
style={[
|
style={[
|
||||||
a.relative,
|
a.relative,
|
||||||
a.text_center,
|
a.text_center,
|
||||||
|
a.font_heavy,
|
||||||
{
|
{
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
fontSize: getFontSize(userNumber),
|
fontSize: getFontSize(userNumber),
|
||||||
lineHeight: getFontSize(userNumber),
|
lineHeight: getFontSize(userNumber),
|
||||||
fontWeight: '900',
|
|
||||||
letterSpacing: -2,
|
letterSpacing: -2,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
@@ -536,7 +536,7 @@ export function TenMillionInner({
|
|||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
a.font_semibold,
|
a.font_bold,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
lightTheme.atoms.text_contrast_medium,
|
lightTheme.atoms.text_contrast_medium,
|
||||||
]}>
|
]}>
|
||||||
@@ -551,7 +551,7 @@ export function TenMillionInner({
|
|||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.text_sm,
|
a.text_sm,
|
||||||
a.font_semibold,
|
a.font_bold,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
a.text_right,
|
a.text_right,
|
||||||
lightTheme.atoms.text_contrast_low,
|
lightTheme.atoms.text_contrast_low,
|
||||||
@@ -643,14 +643,7 @@ export function TenMillionInner({
|
|||||||
<View style={[gtMobile ? a.p_2xl : a.p_xl]}>
|
<View style={[gtMobile ? a.p_2xl : a.p_xl]}>
|
||||||
<Text
|
<Text
|
||||||
allowFontScaling={false}
|
allowFontScaling={false}
|
||||||
style={[
|
style={[a.text_5xl, a.leading_tight, a.pb_lg, a.font_heavy]}>
|
||||||
a.text_5xl,
|
|
||||||
a.leading_tight,
|
|
||||||
a.pb_lg,
|
|
||||||
{
|
|
||||||
fontWeight: '900',
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Trans>Thanks for being one of our first 10 million users.</Trans>
|
<Trans>Thanks for being one of our first 10 million users.</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function DialogInner({
|
|||||||
<Button
|
<Button
|
||||||
label={_(msg`Start chatting`)}
|
label={_(msg`Start chatting`)}
|
||||||
accessibilityHint={_(msg`Close modal`)}
|
accessibilityHint={_(msg`Close modal`)}
|
||||||
size="medium"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onPress={() => control.close()}>
|
onPress={() => control.close()}>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function DateField({
|
|||||||
<Button
|
<Button
|
||||||
label={_(msg`Done`)}
|
label={_(msg`Done`)}
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
size="medium"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid">
|
variant="solid">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ function Inner({control}: {control: DialogControlProps}) {
|
|||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color={status === 'failure' ? 'secondary' : 'primary'}
|
color={status === 'failure' ? 'secondary' : 'primary'}
|
||||||
size="medium"
|
size="large"
|
||||||
style={{marginLeft: 'auto'}}>
|
style={{marginLeft: 'auto'}}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Close</Trans>
|
<Trans>Close</Trans>
|
||||||
@@ -124,7 +124,7 @@ function Inner({control}: {control: DialogControlProps}) {
|
|||||||
onPress={onPressResendEmail}
|
onPress={onPressResendEmail}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
disabled={sending}>
|
disabled={sending}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Resend Email</Trans>
|
<Trans>Resend Email</Trans>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function ContentHider({
|
|||||||
a.text_left,
|
a.text_left,
|
||||||
a.font_bold,
|
a.font_bold,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
gtMobile && [a.font_semibold],
|
gtMobile && [a.font_bold],
|
||||||
t.atoms.text_contrast_medium,
|
t.atoms.text_contrast_medium,
|
||||||
web({
|
web({
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
@@ -107,7 +107,7 @@ export function ContentHider({
|
|||||||
style={[
|
style={[
|
||||||
a.font_bold,
|
a.font_bold,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
gtMobile && [a.font_semibold],
|
gtMobile && [a.font_bold],
|
||||||
t.atoms.text_contrast_high,
|
t.atoms.text_contrast_high,
|
||||||
web({
|
web({
|
||||||
marginBottom: 1,
|
marginBottom: 1,
|
||||||
|
|||||||
@@ -236,8 +236,7 @@ export function LabelerLabelPreference({
|
|||||||
<View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
|
<View style={[a.flex_row, a.gap_xs, a.align_center, a.mt_xs]}>
|
||||||
<CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
|
<CircleInfo size="sm" fill={t.atoms.text_contrast_high.color} />
|
||||||
|
|
||||||
<Text
|
<Text style={[t.atoms.text_contrast_medium, a.font_bold, a.italic]}>
|
||||||
style={[t.atoms.text_contrast_medium, a.font_semibold, a.italic]}>
|
|
||||||
{adultDisabled ? (
|
{adultDisabled ? (
|
||||||
<Trans>Adult content is disabled.</Trans>
|
<Trans>Adult content is disabled.</Trans>
|
||||||
) : isGlobalLabel ? (
|
) : isGlobalLabel ? (
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ function AppealForm({
|
|||||||
testID="backBtn"
|
testID="backBtn"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
label={_(msg`Back`)}>
|
label={_(msg`Back`)}>
|
||||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
@@ -288,7 +288,7 @@ function AppealForm({
|
|||||||
testID="submitBtn"
|
testID="submitBtn"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onSubmit}
|
onPress={onSubmit}
|
||||||
label={_(msg`Submit`)}>
|
label={_(msg`Submit`)}>
|
||||||
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
|
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, useTheme, web} from '#/alf'
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
@@ -86,13 +86,7 @@ export function ScreenHider({
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[a.text_4xl, a.font_bold, a.text_center, a.mb_md, t.atoms.text]}>
|
||||||
a.text_4xl,
|
|
||||||
a.font_semibold,
|
|
||||||
a.text_center,
|
|
||||||
a.mb_md,
|
|
||||||
t.atoms.text,
|
|
||||||
]}>
|
|
||||||
{isNoPwi ? (
|
{isNoPwi ? (
|
||||||
<Trans>Sign-in Required</Trans>
|
<Trans>Sign-in Required</Trans>
|
||||||
) : (
|
) : (
|
||||||
@@ -118,7 +112,7 @@ export function ScreenHider({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_lg,
|
a.text_lg,
|
||||||
a.font_semibold,
|
a.font_bold,
|
||||||
a.leading_snug,
|
a.leading_snug,
|
||||||
t.atoms.text,
|
t.atoms.text,
|
||||||
a.ml_xs,
|
a.ml_xs,
|
||||||
|
|||||||
@@ -33,6 +33,20 @@ export function isJustAMute(modui: ModerationUI): boolean {
|
|||||||
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
return modui.filters.length === 1 && modui.filters[0].type === 'muted'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function moduiContainsHideableOffense(modui: ModerationUI): boolean {
|
||||||
|
const label = modui.filters.at(0)
|
||||||
|
if (label && label.type === 'label') {
|
||||||
|
return labelIsHideableOffense(label.label)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function labelIsHideableOffense(
|
||||||
|
label: ComAtprotoLabelDefs.Label,
|
||||||
|
): boolean {
|
||||||
|
return ['!hide', '!takedown'].includes(label.val)
|
||||||
|
}
|
||||||
|
|
||||||
export function getLabelingServiceTitle({
|
export function getLabelingServiceTitle({
|
||||||
displayName,
|
displayName,
|
||||||
handle,
|
handle,
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import {describe, expect, it} from '@jest/globals'
|
||||||
|
import tldts from 'tldts'
|
||||||
|
|
||||||
|
import {isEmailMaybeInvalid} from '#/lib/strings/email'
|
||||||
|
|
||||||
|
describe('emailTypoChecker', () => {
|
||||||
|
const invalidCases = [
|
||||||
|
'gnail.com',
|
||||||
|
'gnail.co',
|
||||||
|
'gmaill.com',
|
||||||
|
'gmaill.co',
|
||||||
|
'gmai.com',
|
||||||
|
'gmai.co',
|
||||||
|
'gmal.com',
|
||||||
|
'gmal.co',
|
||||||
|
'gmail.co',
|
||||||
|
'iclod.com',
|
||||||
|
'iclod.co',
|
||||||
|
'outllok.com',
|
||||||
|
'outllok.co',
|
||||||
|
'outlook.co',
|
||||||
|
'yaoo.com',
|
||||||
|
'yaoo.co',
|
||||||
|
'yaho.com',
|
||||||
|
'yaho.co',
|
||||||
|
'yahooo.com',
|
||||||
|
'yahooo.co',
|
||||||
|
'yahoo.co',
|
||||||
|
'hithere.jul',
|
||||||
|
'agpowj.notshop',
|
||||||
|
'thisisnot.avalid.tld.nope',
|
||||||
|
// old tld for czechoslovakia
|
||||||
|
'czechoslovakia.cs',
|
||||||
|
// tlds that cbs was registering in 2024 but cancelled
|
||||||
|
'liveon.cbs',
|
||||||
|
'its.showtime',
|
||||||
|
]
|
||||||
|
const validCases = [
|
||||||
|
'gmail.com',
|
||||||
|
// subdomains (tests end of string)
|
||||||
|
'gnail.com.test.com',
|
||||||
|
'outlook.com',
|
||||||
|
'yahoo.com',
|
||||||
|
'icloud.com',
|
||||||
|
'firefox.com',
|
||||||
|
'firefox.co',
|
||||||
|
'hello.world.com',
|
||||||
|
'buy.me.a.coffee.shop',
|
||||||
|
'mayotte.yt',
|
||||||
|
'aland.ax',
|
||||||
|
'bouvet.bv',
|
||||||
|
'uk.gb',
|
||||||
|
'chad.td',
|
||||||
|
'somalia.so',
|
||||||
|
'plane.aero',
|
||||||
|
'cute.cat',
|
||||||
|
'together.coop',
|
||||||
|
'findme.jobs',
|
||||||
|
'nightatthe.museum',
|
||||||
|
'industrial.mil',
|
||||||
|
'czechrepublic.cz',
|
||||||
|
'lovakia.sk',
|
||||||
|
// new gtlds in 2024
|
||||||
|
'whatsinyour.locker',
|
||||||
|
'letsmakea.deal',
|
||||||
|
'skeet.now',
|
||||||
|
'everyone.みんな',
|
||||||
|
'bourgeois.lifestyle',
|
||||||
|
'california.living',
|
||||||
|
'skeet.ing',
|
||||||
|
'listeningto.music',
|
||||||
|
'createa.meme',
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(invalidCases)(`should be invalid: abcde@%s`, domain => {
|
||||||
|
expect(isEmailMaybeInvalid(`abcde@${domain}`, tldts)).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each(validCases)(`should be valid: abcde@%s`, domain => {
|
||||||
|
expect(isEmailMaybeInvalid(`abcde@${domain}`, tldts)).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type tldts from 'tldts'
|
||||||
|
|
||||||
|
const COMMON_ERROR_PATTERN =
|
||||||
|
/([a-zA-Z0-9._%+-]+)@(gnail\.(co|com)|gmaill\.(co|com)|gmai\.(co|com)|gmail\.co|gmal\.(co|com)|iclod\.(co|com)|icloud\.co|outllok\.(co|com)|outlok\.(co|com)|outlook\.co|yaoo\.(co|com)|yaho\.(co|com)|yahoo\.co|yahooo\.(co|com))$/
|
||||||
|
|
||||||
|
export function isEmailMaybeInvalid(email: string, dynamicTldts: typeof tldts) {
|
||||||
|
const isIcann = dynamicTldts.parse(email).isIcann
|
||||||
|
return !isIcann || COMMON_ERROR_PATTERN.test(email)
|
||||||
|
}
|
||||||
+4
-5
@@ -1,6 +1,6 @@
|
|||||||
import {Dimensions, StyleProp, StyleSheet, TextStyle} from 'react-native'
|
import {Dimensions, StyleProp, StyleSheet, TextStyle} from 'react-native'
|
||||||
|
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {Theme, TypographyVariant} from './ThemeContext'
|
import {Theme, TypographyVariant} from './ThemeContext'
|
||||||
|
|
||||||
// 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest
|
// 1 is lightest, 2 is light, 3 is mid, 4 is dark, 5 is darkest
|
||||||
@@ -79,14 +79,13 @@ export const s = StyleSheet.create({
|
|||||||
|
|
||||||
// font weights
|
// font weights
|
||||||
fw600: {fontWeight: '600'},
|
fw600: {fontWeight: '600'},
|
||||||
bold: {fontWeight: '700'},
|
bold: {fontWeight: '600'},
|
||||||
fw500: {fontWeight: '500'},
|
fw500: {fontWeight: '600'},
|
||||||
semiBold: {fontWeight: '500'},
|
semiBold: {fontWeight: '600'},
|
||||||
fw400: {fontWeight: '400'},
|
fw400: {fontWeight: '400'},
|
||||||
normal: {fontWeight: '400'},
|
normal: {fontWeight: '400'},
|
||||||
fw300: {fontWeight: '400'},
|
fw300: {fontWeight: '400'},
|
||||||
light: {fontWeight: '400'},
|
light: {fontWeight: '400'},
|
||||||
fw200: {fontWeight: '200'},
|
|
||||||
|
|
||||||
// text decoration
|
// text decoration
|
||||||
underline: {textDecorationLine: 'underline'},
|
underline: {textDecorationLine: 'underline'},
|
||||||
|
|||||||
+19
-19
@@ -100,12 +100,12 @@ export const defaultTheme: Theme = {
|
|||||||
'2xl-medium': {
|
'2xl-medium': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'2xl-bold': {
|
'2xl-bold': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'2xl-heavy': {
|
'2xl-heavy': {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -125,12 +125,12 @@ export const defaultTheme: Theme = {
|
|||||||
'xl-medium': {
|
'xl-medium': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'xl-bold': {
|
'xl-bold': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'xl-heavy': {
|
'xl-heavy': {
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
@@ -150,12 +150,12 @@ export const defaultTheme: Theme = {
|
|||||||
'lg-medium': {
|
'lg-medium': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'lg-bold': {
|
'lg-bold': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'lg-heavy': {
|
'lg-heavy': {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -175,12 +175,12 @@ export const defaultTheme: Theme = {
|
|||||||
'md-medium': {
|
'md-medium': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'md-bold': {
|
'md-bold': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'md-heavy': {
|
'md-heavy': {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
@@ -200,12 +200,12 @@ export const defaultTheme: Theme = {
|
|||||||
'sm-medium': {
|
'sm-medium': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'sm-bold': {
|
'sm-bold': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'sm-heavy': {
|
'sm-heavy': {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -225,12 +225,12 @@ export const defaultTheme: Theme = {
|
|||||||
'xs-medium': {
|
'xs-medium': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'xs-bold': {
|
'xs-bold': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '700',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'xs-heavy': {
|
'xs-heavy': {
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -241,24 +241,24 @@ export const defaultTheme: Theme = {
|
|||||||
'title-2xl': {
|
'title-2xl': {
|
||||||
fontSize: 34,
|
fontSize: 34,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'title-xl': {
|
'title-xl': {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
'title-lg': {
|
'title-lg': {
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
'title-sm': {
|
'title-sm': {
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
@@ -273,12 +273,12 @@ export const defaultTheme: Theme = {
|
|||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
},
|
},
|
||||||
'button-lg': {
|
'button-lg': {
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
fontWeight: '500',
|
fontWeight: '600',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
letterSpacing: tokens.TRACKING,
|
letterSpacing: tokens.TRACKING,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import {getLocales as defaultGetLocales, Locale} from 'expo-localization'
|
||||||
|
|
||||||
|
import {dedupArray} from '#/lib/functions'
|
||||||
|
|
||||||
|
type LocalWithLanguageCode = Locale & {
|
||||||
|
languageCode: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalized locales
|
||||||
|
*
|
||||||
|
* Handles legacy migration for Java devices.
|
||||||
|
*
|
||||||
|
* {@link https://github.com/bluesky-social/social-app/pull/4461}
|
||||||
|
* {@link https://xml.coverpages.org/iso639a.html}
|
||||||
|
*/
|
||||||
|
export function getLocales() {
|
||||||
|
const locales = defaultGetLocales?.() ?? []
|
||||||
|
const output: LocalWithLanguageCode[] = []
|
||||||
|
|
||||||
|
for (const locale of locales) {
|
||||||
|
if (typeof locale.languageCode === 'string') {
|
||||||
|
if (locale.languageCode === 'in') {
|
||||||
|
// indonesian
|
||||||
|
locale.languageCode = 'id'
|
||||||
|
}
|
||||||
|
if (locale.languageCode === 'iw') {
|
||||||
|
// hebrew
|
||||||
|
locale.languageCode = 'he'
|
||||||
|
}
|
||||||
|
if (locale.languageCode === 'ji') {
|
||||||
|
// yiddish
|
||||||
|
locale.languageCode = 'yi'
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore checked above
|
||||||
|
output.push(locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deviceLocales = getLocales()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BCP-47 language tag without region e.g. array of 2-char lang codes
|
||||||
|
*
|
||||||
|
* {@link https://docs.expo.dev/versions/latest/sdk/localization/#locale}
|
||||||
|
*/
|
||||||
|
export const deviceLanguageCodes = dedupArray(
|
||||||
|
deviceLocales.map(l => l.languageCode),
|
||||||
|
)
|
||||||
+23
-1
@@ -160,8 +160,13 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
|||||||
return AppLanguage.en
|
return AppLanguage.en
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles legacy migration for Java devices.
|
||||||
|
*
|
||||||
|
* {@link https://github.com/bluesky-social/social-app/pull/4461}
|
||||||
|
* {@link https://xml.coverpages.org/iso639a.html}
|
||||||
|
*/
|
||||||
export function fixLegacyLanguageCode(code: string | null): string | null {
|
export function fixLegacyLanguageCode(code: string | null): string | null {
|
||||||
// handle some legacy code conversions, see https://xml.coverpages.org/iso639a.html
|
|
||||||
if (code === 'in') {
|
if (code === 'in') {
|
||||||
// indonesian
|
// indonesian
|
||||||
return 'id'
|
return 'id'
|
||||||
@@ -176,3 +181,20 @@ export function fixLegacyLanguageCode(code: string | null): string | null {
|
|||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first language supported by our translation infra. Values should be
|
||||||
|
* in order of preference, and match the values of {@link AppLanguage}.
|
||||||
|
*
|
||||||
|
* If no match, returns `en`.
|
||||||
|
*/
|
||||||
|
export function findSupportedAppLanguage(languageTags: (string | undefined)[]) {
|
||||||
|
const supported = new Set(Object.values(AppLanguage))
|
||||||
|
for (const tag of languageTags) {
|
||||||
|
if (!tag) continue
|
||||||
|
if (supported.has(tag as AppLanguage)) {
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AppLanguage.en
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
import {getLocales} from 'expo-localization'
|
|
||||||
|
|
||||||
import {fixLegacyLanguageCode} from '#/locale/helpers'
|
|
||||||
import {dedupArray} from 'lib/functions'
|
|
||||||
|
|
||||||
export const isIOS = Platform.OS === 'ios'
|
export const isIOS = Platform.OS === 'ios'
|
||||||
export const isAndroid = Platform.OS === 'android'
|
export const isAndroid = Platform.OS === 'android'
|
||||||
@@ -15,9 +11,3 @@ export const isMobileWeb =
|
|||||||
// @ts-ignore we know window exists -prf
|
// @ts-ignore we know window exists -prf
|
||||||
global.window.matchMedia(isMobileWebMediaQuery)?.matches
|
global.window.matchMedia(isMobileWebMediaQuery)?.matches
|
||||||
export const isIPhoneWeb = isWeb && /iPhone/.test(navigator.userAgent)
|
export const isIPhoneWeb = isWeb && /iPhone/.test(navigator.userAgent)
|
||||||
|
|
||||||
export const deviceLocales = dedupArray(
|
|
||||||
getLocales?.()
|
|
||||||
.map?.(locale => fixLegacyLanguageCode(locale.languageCode))
|
|
||||||
.filter(code => typeof code === 'string'),
|
|
||||||
) as string[]
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export function Deactivated() {
|
|||||||
<View style={[a.gap_sm]}>
|
<View style={[a.gap_sm]}>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Reactivate your account`)}
|
label={_(msg`Reactivate your account`)}
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={handleActivate}>
|
onPress={handleActivate}>
|
||||||
@@ -153,7 +153,7 @@ export function Deactivated() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Cancel reactivation and log out`)}
|
label={_(msg`Cancel reactivation and log out`)}
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onPress={onPressLogout}>
|
onPress={onPressLogout}>
|
||||||
@@ -212,7 +212,7 @@ export function Deactivated() {
|
|||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Log in or sign up`)}
|
label={_(msg`Log in or sign up`)}
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onPress={() => setShowLoggedOut(true)}>
|
onPress={() => setShowLoggedOut(true)}>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeValue('testerString')
|
SharedPrefs.removeValue('testerString')
|
||||||
SharedPrefs.setValue('testerString', 'Hello')
|
SharedPrefs.setValue('testerString', 'Hello')
|
||||||
@@ -39,7 +39,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeValue('testerString')
|
SharedPrefs.removeValue('testerString')
|
||||||
const str = SharedPrefs.getString('testerString')
|
const str = SharedPrefs.getString('testerString')
|
||||||
@@ -53,7 +53,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeValue('testerBool')
|
SharedPrefs.removeValue('testerBool')
|
||||||
SharedPrefs.setValue('testerBool', true)
|
SharedPrefs.setValue('testerBool', true)
|
||||||
@@ -68,7 +68,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeValue('testerNumber')
|
SharedPrefs.removeValue('testerNumber')
|
||||||
SharedPrefs.setValue('testerNumber', 123)
|
SharedPrefs.setValue('testerNumber', 123)
|
||||||
@@ -83,7 +83,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||||
SharedPrefs.addToSet('testerSet', 'Hello!')
|
SharedPrefs.addToSet('testerSet', 'Hello!')
|
||||||
@@ -98,7 +98,7 @@ export function SharedPreferencesTesterScreen() {
|
|||||||
style={[a.self_center]}
|
style={[a.self_center]}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||||
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function NoFeedsPinned({
|
|||||||
<Button
|
<Button
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
label={_(msg`Apply default recommended feeds`)}
|
label={_(msg`Apply default recommended feeds`)}
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={addRecommendedFeeds}>
|
onPress={addRecommendedFeeds}>
|
||||||
@@ -102,7 +102,7 @@ export function NoFeedsPinned({
|
|||||||
<Link
|
<Link
|
||||||
label={_(msg`Browse other feeds`)}
|
label={_(msg`Browse other feeds`)}
|
||||||
to="/feeds"
|
to="/feeds"
|
||||||
size="medium"
|
size="large"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary">
|
color="secondary">
|
||||||
<ButtonIcon icon={ListSparkle} position="left" />
|
<ButtonIcon icon={ListSparkle} position="left" />
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export function ListHiddenScreen({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
label={_(msg`Remove from saved feeds`)}
|
label={_(msg`Remove from saved feeds`)}
|
||||||
onPress={onRemoveList}
|
onPress={onRemoveList}
|
||||||
disabled={isProcessing}>
|
disabled={isProcessing}>
|
||||||
@@ -168,7 +168,7 @@ export function ListHiddenScreen({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
label={_(msg`Show list anyway`)}
|
label={_(msg`Show list anyway`)}
|
||||||
onPress={() => setIsContentVisible(true)}
|
onPress={() => setIsContentVisible(true)}
|
||||||
disabled={isProcessing}>
|
disabled={isProcessing}>
|
||||||
@@ -180,7 +180,7 @@ export function ListHiddenScreen({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
label={_(msg`Unsubscribe from list`)}
|
label={_(msg`Unsubscribe from list`)}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (isModList) {
|
if (isModList) {
|
||||||
@@ -204,7 +204,7 @@ export function ListHiddenScreen({
|
|||||||
color="primary"
|
color="primary"
|
||||||
label={_(msg`Return to previous page`)}
|
label={_(msg`Return to previous page`)}
|
||||||
onPress={goBack}
|
onPress={goBack}
|
||||||
size="medium"
|
size="large"
|
||||||
disabled={isProcessing}>
|
disabled={isProcessing}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Go Back</Trans>
|
<Trans>Go Back</Trans>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const ChooseAccountForm = ({
|
|||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export const ForgotPasswordForm = ({
|
|||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
@@ -143,7 +143,7 @@ export const ForgotPasswordForm = ({
|
|||||||
label={_(msg`Next`)}
|
label={_(msg`Next`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color={'primary'}
|
color={'primary'}
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Next</Trans>
|
||||||
@@ -170,7 +170,7 @@ export const ForgotPasswordForm = ({
|
|||||||
onPress={onEmailSent}
|
onPress={onEmailSent}
|
||||||
label={_(msg`Go to next`)}
|
label={_(msg`Go to next`)}
|
||||||
accessibilityHint={_(msg`Navigates to the next screen`)}
|
accessibilityHint={_(msg`Navigates to the next screen`)}
|
||||||
size="medium"
|
size="large"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="secondary">
|
color="secondary">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ export const LoginForm = ({
|
|||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
@@ -299,7 +299,7 @@ export const LoginForm = ({
|
|||||||
accessibilityHint={_(msg`Retries login`)}
|
accessibilityHint={_(msg`Retries login`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressRetryConnect}>
|
onPress={onPressRetryConnect}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Retry</Trans>
|
<Trans>Retry</Trans>
|
||||||
@@ -319,7 +319,7 @@ export const LoginForm = ({
|
|||||||
accessibilityHint={_(msg`Navigates to the next screen`)}
|
accessibilityHint={_(msg`Navigates to the next screen`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Next</Trans>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const PasswordUpdatedForm = ({
|
|||||||
accessibilityHint={_(msg`Closes password update alert`)}
|
accessibilityHint={_(msg`Closes password update alert`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium">
|
size="large">
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Okay</Trans>
|
<Trans>Okay</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export const SetNewPasswordForm = ({
|
|||||||
label={_(msg`Back`)}
|
label={_(msg`Back`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
@@ -174,7 +174,7 @@ export const SetNewPasswordForm = ({
|
|||||||
label={_(msg`Next`)}
|
label={_(msg`Next`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onPressNext}>
|
onPress={onPressNext}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
<Trans>Next</Trans>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function DialogInner() {
|
|||||||
testID="backBtn"
|
testID="backBtn"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onBack}
|
onPress={onBack}
|
||||||
label={_(msg`Back`)}>
|
label={_(msg`Back`)}>
|
||||||
<ButtonText>{_(msg`Back`)}</ButtonText>
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
@@ -137,7 +137,7 @@ function DialogInner() {
|
|||||||
testID="submitBtn"
|
testID="submitBtn"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onSubmit}
|
onPress={onSubmit}
|
||||||
label={_(msg`Submit`)}>
|
label={_(msg`Submit`)}>
|
||||||
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Reload conversations`)}
|
label={_(msg`Reload conversations`)}
|
||||||
size="medium"
|
size="large"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onPress={() => refetch()}>
|
onPress={() => refetch()}>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {getLabelingServiceTitle} from '#/lib/moderation'
|
import {getLabelingServiceTitle} from '#/lib/moderation'
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
@@ -22,8 +23,8 @@ import {
|
|||||||
useProfileUpdateMutation,
|
useProfileUpdateMutation,
|
||||||
} from '#/state/queries/profile'
|
} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
|
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
@@ -338,7 +339,7 @@ export function ModerationScreenInner({
|
|||||||
a.justify_between,
|
a.justify_between,
|
||||||
disabledOnIOS && {opacity: 0.5},
|
disabledOnIOS && {opacity: 0.5},
|
||||||
]}>
|
]}>
|
||||||
<Text style={[a.font_semibold, t.atoms.text_contrast_high]}>
|
<Text style={[a.font_bold, t.atoms.text_contrast_high]}>
|
||||||
<Trans>Enable adult content</Trans>
|
<Trans>Enable adult content</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Toggle.Item
|
<Toggle.Item
|
||||||
@@ -455,6 +456,9 @@ export function ModerationScreenInner({
|
|||||||
value={labeler.creator.description}
|
value={labeler.creator.description}
|
||||||
handle={labeler.creator.handle}
|
handle={labeler.creator.handle}
|
||||||
/>
|
/>
|
||||||
|
{isNonConfigurableModerationAuthority(
|
||||||
|
labeler.creator.did,
|
||||||
|
) && <LabelingService.RegionalNotice />}
|
||||||
</LabelingService.Content>
|
</LabelingService.Content>
|
||||||
</LabelingService.Outer>
|
</LabelingService.Outer>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React from 'react'
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
|
import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
|
||||||
|
|
||||||
|
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||||
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export function ProfileHeaderDisplayName({
|
|||||||
<View pointerEvents="none">
|
<View pointerEvents="none">
|
||||||
<Text
|
<Text
|
||||||
testID="profileHeaderDisplayName"
|
testID="profileHeaderDisplayName"
|
||||||
style={[t.atoms.text, a.text_4xl, a.self_start, {fontWeight: '500'}]}>
|
style={[t.atoms.text, a.text_4xl, a.self_start, {fontWeight: '600'}]}>
|
||||||
{sanitizeDisplayName(
|
{sanitizeDisplayName(
|
||||||
profile.displayName || sanitizeHandle(profile.handle),
|
profile.displayName || sanitizeHandle(profile.handle),
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function DeactivateAccountDialogInner({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="negative"
|
color="negative"
|
||||||
size={gtMobile ? 'small' : 'medium'}
|
size={gtMobile ? 'small' : 'large'}
|
||||||
label={_(msg`Yes, deactivate`)}
|
label={_(msg`Yes, deactivate`)}
|
||||||
onPress={handleDeactivate}>
|
onPress={handleDeactivate}>
|
||||||
<ButtonText>{_(msg`Yes, deactivate`)}</ButtonText>
|
<ButtonText>{_(msg`Yes, deactivate`)}</ButtonText>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface BackNextButtonsProps {
|
|||||||
onBackPress: () => void
|
onBackPress: () => void
|
||||||
onNextPress?: () => void
|
onNextPress?: () => void
|
||||||
onRetryPress?: () => void
|
onRetryPress?: () => void
|
||||||
|
overrideNextText?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BackNextButtons({
|
export function BackNextButtons({
|
||||||
@@ -25,6 +26,7 @@ export function BackNextButtons({
|
|||||||
onBackPress,
|
onBackPress,
|
||||||
onNextPress,
|
onNextPress,
|
||||||
onRetryPress,
|
onRetryPress,
|
||||||
|
overrideNextText,
|
||||||
}: BackNextButtonsProps) {
|
}: BackNextButtonsProps) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ export function BackNextButtons({
|
|||||||
label={_(msg`Go back to previous step`)}
|
label={_(msg`Go back to previous step`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onBackPress}>
|
onPress={onBackPress}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Back</Trans>
|
<Trans>Back</Trans>
|
||||||
@@ -46,7 +48,7 @@ export function BackNextButtons({
|
|||||||
label={_(msg`Press to retry`)}
|
label={_(msg`Press to retry`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
onPress={onRetryPress}>
|
onPress={onRetryPress}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Retry</Trans>
|
<Trans>Retry</Trans>
|
||||||
@@ -59,11 +61,11 @@ export function BackNextButtons({
|
|||||||
label={_(msg`Continue to next step`)}
|
label={_(msg`Continue to next step`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
disabled={isLoading || isNextDisabled}
|
disabled={isLoading || isNextDisabled}
|
||||||
onPress={onNextPress}>
|
onPress={onNextPress}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Next</Trans>
|
{overrideNextText ? overrideNextText : <Trans>Next</Trans>}
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
{isLoading && <ButtonIcon icon={Loader} />}
|
{isLoading && <ButtonIcon icon={Loader} />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import {View} from 'react-native'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import * as EmailValidator from 'email-validator'
|
import * as EmailValidator from 'email-validator'
|
||||||
|
import type tldts from 'tldts'
|
||||||
|
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {isEmailMaybeInvalid} from 'lib/strings/email'
|
||||||
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
|
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
|
||||||
import {is13, is18, useSignupContext} from '#/screens/Signup/state'
|
import {is13, is18, useSignupContext} from '#/screens/Signup/state'
|
||||||
import {Policies} from '#/screens/Signup/StepInfo/Policies'
|
import {Policies} from '#/screens/Signup/StepInfo/Policies'
|
||||||
@@ -46,13 +48,41 @@ export function StepInfo({
|
|||||||
|
|
||||||
const inviteCodeValueRef = useRef<string>(state.inviteCode)
|
const inviteCodeValueRef = useRef<string>(state.inviteCode)
|
||||||
const emailValueRef = useRef<string>(state.email)
|
const emailValueRef = useRef<string>(state.email)
|
||||||
|
const prevEmailValueRef = useRef<string>(state.email)
|
||||||
const passwordValueRef = useRef<string>(state.password)
|
const passwordValueRef = useRef<string>(state.password)
|
||||||
|
|
||||||
const onNextPress = React.useCallback(async () => {
|
const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false)
|
||||||
|
|
||||||
|
const tldtsRef = React.useRef<typeof tldts>()
|
||||||
|
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 inviteCode = inviteCodeValueRef.current
|
||||||
const email = emailValueRef.current
|
const email = emailValueRef.current
|
||||||
|
const emailChanged = prevEmailValueRef.current !== email
|
||||||
const password = passwordValueRef.current
|
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)) {
|
if (!is13(state.dateOfBirth)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -89,13 +119,7 @@ export function StepInfo({
|
|||||||
logEvent('signup:nextPressed', {
|
logEvent('signup:nextPressed', {
|
||||||
activeStep: state.activeStep,
|
activeStep: state.activeStep,
|
||||||
})
|
})
|
||||||
}, [
|
}
|
||||||
_,
|
|
||||||
dispatch,
|
|
||||||
state.activeStep,
|
|
||||||
state.dateOfBirth,
|
|
||||||
state.serviceDescription?.inviteCodeRequired,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenTransition>
|
<ScreenTransition>
|
||||||
@@ -148,6 +172,9 @@ export function StepInfo({
|
|||||||
testID="emailInput"
|
testID="emailInput"
|
||||||
onChangeText={value => {
|
onChangeText={value => {
|
||||||
emailValueRef.current = value.trim()
|
emailValueRef.current = value.trim()
|
||||||
|
if (hasWarnedEmail) {
|
||||||
|
setHasWarnedEmail(false)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
label={_(msg`Enter your email address`)}
|
label={_(msg`Enter your email address`)}
|
||||||
defaultValue={state.email}
|
defaultValue={state.email}
|
||||||
@@ -208,6 +235,7 @@ export function StepInfo({
|
|||||||
onBackPress={onPressBack}
|
onBackPress={onPressBack}
|
||||||
onNextPress={onNextPress}
|
onNextPress={onNextPress}
|
||||||
onRetryPress={refetchServer}
|
onRetryPress={refetchServer}
|
||||||
|
overrideNextText={hasWarnedEmail ? _(msg`It's correct`) : undefined}
|
||||||
/>
|
/>
|
||||||
</ScreenTransition>
|
</ScreenTransition>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {FEEDBACK_FORM_URL} from '#/lib/constants'
|
import {FEEDBACK_FORM_URL} from '#/lib/constants'
|
||||||
import {useServiceQuery} from '#/state/queries/service'
|
import {useServiceQuery} from '#/state/queries/service'
|
||||||
import {useStarterPackQuery} from 'state/queries/starter-packs'
|
import {useStarterPackQuery} from '#/state/queries/starter-packs'
|
||||||
import {useActiveStarterPack} from 'state/shell/starter-pack'
|
import {useActiveStarterPack} from '#/state/shell/starter-pack'
|
||||||
import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
|
import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
|
||||||
import {
|
import {
|
||||||
initialState,
|
initialState,
|
||||||
@@ -132,7 +132,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
|
|||||||
!gtMobile && {paddingBottom: 100},
|
!gtMobile && {paddingBottom: 100},
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.gap_sm, a.pb_3xl]}>
|
<View style={[a.gap_sm, a.pb_3xl]}>
|
||||||
<Text style={[a.font_semibold, t.atoms.text_contrast_medium]}>
|
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Step {state.activeStep + 1} of{' '}
|
Step {state.activeStep + 1} of{' '}
|
||||||
{state.serviceDescription &&
|
{state.serviceDescription &&
|
||||||
|
|||||||
@@ -11,22 +11,22 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {isAndroidWeb} from '#/lib/browser'
|
||||||
import {JOINED_THIS_WEEK} from '#/lib/constants'
|
import {JOINED_THIS_WEEK} from '#/lib/constants'
|
||||||
import {isAndroidWeb} from 'lib/browser'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {logEvent} from 'lib/statsig/statsig'
|
import {createStarterPackGooglePlayUri} from '#/lib/strings/starter-pack'
|
||||||
import {createStarterPackGooglePlayUri} from 'lib/strings/starter-pack'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {isWeb} from 'platform/detection'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useModerationOpts} from 'state/preferences/moderation-opts'
|
import {useStarterPackQuery} from '#/state/queries/starter-packs'
|
||||||
import {useStarterPackQuery} from 'state/queries/starter-packs'
|
|
||||||
import {
|
import {
|
||||||
useActiveStarterPack,
|
useActiveStarterPack,
|
||||||
useSetActiveStarterPack,
|
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 {formatCount} from '#/view/com/util/numeric/format'
|
||||||
import {LoggedOutScreenState} from 'view/com/auth/LoggedOut'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {Logo} from 'view/icons/Logo'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
@@ -188,12 +188,7 @@ function LandingScreenLoaded({
|
|||||||
{record.name}
|
{record.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[a.text_center, a.font_bold, a.text_md, {color: 'white'}]}>
|
||||||
a.text_center,
|
|
||||||
a.font_semibold,
|
|
||||||
a.text_md,
|
|
||||||
{color: 'white'},
|
|
||||||
]}>
|
|
||||||
Starter pack by {`@${creator.handle}`}
|
Starter pack by {`@${creator.handle}`}
|
||||||
</Text>
|
</Text>
|
||||||
</LinearGradientBackground>
|
</LinearGradientBackground>
|
||||||
@@ -219,11 +214,7 @@ function LandingScreenLoaded({
|
|||||||
color={t.atoms.text_contrast_medium.color}
|
color={t.atoms.text_contrast_medium.color}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[a.font_bold, a.text_sm, t.atoms.text_contrast_medium]}
|
||||||
a.font_semibold,
|
|
||||||
a.text_sm,
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
]}
|
|
||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
<Trans>
|
<Trans>
|
||||||
{formatCount(i18n, JOINED_THIS_WEEK)} joined this week
|
{formatCount(i18n, JOINED_THIS_WEEK)} joined this week
|
||||||
@@ -308,7 +299,7 @@ function LandingScreenLoaded({
|
|||||||
label={_(msg`Signup without a starter pack`)}
|
label={_(msg`Signup without a starter pack`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="large"
|
||||||
style={[a.py_lg]}
|
style={[a.py_lg]}
|
||||||
onPress={onJoinWithoutPress}>
|
onPress={onJoinWithoutPress}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ function Header({
|
|||||||
}}
|
}}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium">
|
size="large">
|
||||||
<ButtonText style={[a.text_lg]}>
|
<ButtonText style={[a.text_lg]}>
|
||||||
<Trans>Join Bluesky</Trans>
|
<Trans>Join Bluesky</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
@@ -645,7 +645,7 @@ function OverflowMenu({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="negative"
|
color="negative"
|
||||||
size={gtMobile ? 'small' : 'medium'}
|
size={gtMobile ? 'small' : 'large'}
|
||||||
label={_(msg`Yes, delete this starter pack`)}
|
label={_(msg`Yes, delete this starter pack`)}
|
||||||
onPress={onDeleteStarterPack}>
|
onPress={onDeleteStarterPack}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ function Container({children}: {children: React.ReactNode}) {
|
|||||||
label={_(msg`Next`)}
|
label={_(msg`Next`)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="medium"
|
size="large"
|
||||||
style={[a.mx_xl, a.mb_lg, {marginTop: 35}]}
|
style={[a.mx_xl, a.mb_lg, {marginTop: 35}]}
|
||||||
onPress={() => dispatch({type: 'Next'})}>
|
onPress={() => dispatch({type: 'Next'})}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import EventEmitter from 'eventemitter3'
|
||||||
|
|
||||||
|
import {networkRetry} from '#/lib/async/retry'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {IS_DEV} from '#/env'
|
||||||
|
import {Device, device} from '#/storage'
|
||||||
|
|
||||||
|
const events = new EventEmitter()
|
||||||
|
const EVENT = 'geolocation-updated'
|
||||||
|
const emitGeolocationUpdate = (geolocation: Device['geolocation']) => {
|
||||||
|
events.emit(EVENT, geolocation)
|
||||||
|
}
|
||||||
|
const onGeolocationUpdate = (
|
||||||
|
listener: (geolocation: Device['geolocation']) => void,
|
||||||
|
) => {
|
||||||
|
events.on(EVENT, listener)
|
||||||
|
return () => {
|
||||||
|
events.off(EVENT, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default geolocation value. IF undefined, we fail closed and apply all
|
||||||
|
* additional mod authorities.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_GEOLOCATION: Device['geolocation'] = {
|
||||||
|
countryCode: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGeolocation(): Promise<Device['geolocation']> {
|
||||||
|
const res = await fetch(`https://bsky.app/ipcc`)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`geolocation: lookup failed ${res.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json()
|
||||||
|
|
||||||
|
if (json.countryCode) {
|
||||||
|
return {
|
||||||
|
countryCode: json.countryCode,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local promise used within this file only.
|
||||||
|
*/
|
||||||
|
let geolocationResolution: Promise<void> | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin the process of resolving geolocation. This should be called once at
|
||||||
|
* app start.
|
||||||
|
*
|
||||||
|
* THIS METHOD SHOULD NEVER THROW.
|
||||||
|
*
|
||||||
|
* This method is otherwise not used for any purpose. To ensure geolocation is
|
||||||
|
* resolved, use {@link ensureGeolocationResolved}
|
||||||
|
*/
|
||||||
|
export function beginResolveGeolocation() {
|
||||||
|
/**
|
||||||
|
* In dev, IP server is unavailable, so we just set the default geolocation
|
||||||
|
* and fail closed.
|
||||||
|
*/
|
||||||
|
if (IS_DEV) {
|
||||||
|
geolocationResolution = new Promise(y => y())
|
||||||
|
device.set(['geolocation'], DEFAULT_GEOLOCATION)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
geolocationResolution = new Promise(async resolve => {
|
||||||
|
try {
|
||||||
|
// Try once, fail fast
|
||||||
|
const geolocation = await getGeolocation()
|
||||||
|
if (geolocation) {
|
||||||
|
device.set(['geolocation'], geolocation)
|
||||||
|
emitGeolocationUpdate(geolocation)
|
||||||
|
logger.debug(`geolocation: success`, {geolocation})
|
||||||
|
} else {
|
||||||
|
// endpoint should throw on all failures, this is insurance
|
||||||
|
throw new Error(`geolocation: nothing returned from initial request`)
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`geolocation: failed initial request`, {
|
||||||
|
safeMessage: e.message,
|
||||||
|
})
|
||||||
|
|
||||||
|
// set to default
|
||||||
|
device.set(['geolocation'], DEFAULT_GEOLOCATION)
|
||||||
|
|
||||||
|
// retry 3 times, but don't await, proceed with default
|
||||||
|
networkRetry(3, getGeolocation)
|
||||||
|
.then(geolocation => {
|
||||||
|
if (geolocation) {
|
||||||
|
device.set(['geolocation'], geolocation)
|
||||||
|
emitGeolocationUpdate(geolocation)
|
||||||
|
logger.debug(`geolocation: success`, {geolocation})
|
||||||
|
} else {
|
||||||
|
// endpoint should throw on all failures, this is insurance
|
||||||
|
throw new Error(`geolocation: nothing returned from retries`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
// complete fail closed
|
||||||
|
logger.error(`geolocation: failed retries`, {safeMessage: e.message})
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
resolve(undefined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that geolocation has been resolved, or at the very least attempted
|
||||||
|
* once. Subsequent retries will not be captured by this `await`. Those will be
|
||||||
|
* reported via {@link events}.
|
||||||
|
*/
|
||||||
|
export async function ensureGeolocationResolved() {
|
||||||
|
if (!geolocationResolution) {
|
||||||
|
throw new Error(`geolocation: beginResolveGeolocation not called yet`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = device.get(['geolocation'])
|
||||||
|
if (cached) {
|
||||||
|
logger.debug(`geolocation: using cache`, {cached})
|
||||||
|
} else {
|
||||||
|
logger.debug(`geolocation: no cache`)
|
||||||
|
await geolocationResolution
|
||||||
|
logger.debug(`geolocation: resolved`, {
|
||||||
|
resolved: device.get(['geolocation']),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
geolocation: Device['geolocation']
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = React.createContext<Context>({
|
||||||
|
geolocation: DEFAULT_GEOLOCATION,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
|
const [geolocation, setGeolocation] = React.useState(() => {
|
||||||
|
const initial = device.get(['geolocation']) || DEFAULT_GEOLOCATION
|
||||||
|
return initial
|
||||||
|
})
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return onGeolocationUpdate(geolocation => {
|
||||||
|
setGeolocation(geolocation!)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const ctx = React.useMemo(() => {
|
||||||
|
return {
|
||||||
|
geolocation,
|
||||||
|
}
|
||||||
|
}, [geolocation])
|
||||||
|
|
||||||
|
return <context.Provider value={ctx}>{children}</context.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGeolocation() {
|
||||||
|
return React.useContext(context)
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
tryStringify,
|
tryStringify,
|
||||||
} from '#/state/persisted/schema'
|
} from '#/state/persisted/schema'
|
||||||
import {PersistedApi} from './types'
|
import {PersistedApi} from './types'
|
||||||
|
import {normalizeData} from './util'
|
||||||
|
|
||||||
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
|
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
|
||||||
export {defaults} from '#/state/persisted/schema'
|
export {defaults} from '#/state/persisted/schema'
|
||||||
@@ -33,10 +34,10 @@ export async function write<K extends keyof Schema>(
|
|||||||
key: K,
|
key: K,
|
||||||
value: Schema[K],
|
value: Schema[K],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
_state = {
|
_state = normalizeData({
|
||||||
..._state,
|
..._state,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
}
|
})
|
||||||
await writeToStorage(_state)
|
await writeToStorage(_state)
|
||||||
}
|
}
|
||||||
write satisfies PersistedApi['write']
|
write satisfies PersistedApi['write']
|
||||||
@@ -81,6 +82,9 @@ async function readFromStorage(): Promise<Schema | undefined> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (rawData) {
|
if (rawData) {
|
||||||
return tryParse(rawData)
|
const parsed = tryParse(rawData)
|
||||||
|
if (parsed) {
|
||||||
|
return normalizeData(parsed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
tryStringify,
|
tryStringify,
|
||||||
} from '#/state/persisted/schema'
|
} from '#/state/persisted/schema'
|
||||||
import {PersistedApi} from './types'
|
import {PersistedApi} from './types'
|
||||||
|
import {normalizeData} from './util'
|
||||||
|
|
||||||
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
|
export type {PersistedAccount, Schema} from '#/state/persisted/schema'
|
||||||
export {defaults} from '#/state/persisted/schema'
|
export {defaults} from '#/state/persisted/schema'
|
||||||
@@ -56,10 +57,10 @@ export async function write<K extends keyof Schema>(
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore and go through the normal path.
|
// Ignore and go through the normal path.
|
||||||
}
|
}
|
||||||
_state = {
|
_state = normalizeData({
|
||||||
..._state,
|
..._state,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
}
|
})
|
||||||
writeToStorage(_state)
|
writeToStorage(_state)
|
||||||
broadcast.postMessage({event: {type: UPDATE_EVENT, key}})
|
broadcast.postMessage({event: {type: UPDATE_EVENT, key}})
|
||||||
broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading
|
broadcast.postMessage({event: UPDATE_EVENT}) // Backcompat while upgrading
|
||||||
@@ -140,9 +141,11 @@ function readFromStorage(): Schema | undefined {
|
|||||||
return lastResult
|
return lastResult
|
||||||
} else {
|
} else {
|
||||||
const result = tryParse(rawData)
|
const result = tryParse(rawData)
|
||||||
|
if (result) {
|
||||||
lastRawData = rawData
|
lastRawData = rawData
|
||||||
lastResult = result
|
lastResult = normalizeData(result)
|
||||||
return result
|
return lastResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {z} from 'zod'
|
import {z} from 'zod'
|
||||||
|
|
||||||
|
import {deviceLanguageCodes, deviceLocales} from '#/locale/deviceLocales'
|
||||||
|
import {findSupportedAppLanguage} from '#/locale/helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {deviceLocales} from '#/platform/detection'
|
|
||||||
import {PlatformInfo} from '../../../modules/expo-bluesky-swiss-army'
|
import {PlatformInfo} from '../../../modules/expo-bluesky-swiss-army'
|
||||||
|
|
||||||
const externalEmbedOptions = ['show', 'hide'] as const
|
const externalEmbedOptions = ['show', 'hide'] as const
|
||||||
@@ -55,10 +56,39 @@ const schema = z.object({
|
|||||||
lastEmailConfirm: z.string().optional(),
|
lastEmailConfirm: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
languagePrefs: z.object({
|
languagePrefs: z.object({
|
||||||
primaryLanguage: z.string(), // should move to server
|
/**
|
||||||
contentLanguages: z.array(z.string()), // should move to server
|
* The target language for translating posts.
|
||||||
postLanguage: z.string(), // should move to server
|
*
|
||||||
|
* BCP-47 2-letter language code without region.
|
||||||
|
*/
|
||||||
|
primaryLanguage: z.string(),
|
||||||
|
/**
|
||||||
|
* The languages the user can read, passed to feeds.
|
||||||
|
*
|
||||||
|
* BCP-47 2-letter language codes without region.
|
||||||
|
*/
|
||||||
|
contentLanguages: z.array(z.string()),
|
||||||
|
/**
|
||||||
|
* The language(s) the user is currently posting in, configured within the
|
||||||
|
* composer. Multiple languages are psearate by commas.
|
||||||
|
*
|
||||||
|
* BCP-47 2-letter language code without region.
|
||||||
|
*/
|
||||||
|
postLanguage: z.string(),
|
||||||
|
/**
|
||||||
|
* The user's post language history, used to pre-populate the post language
|
||||||
|
* selector in the composer. Within each value, multiple languages are
|
||||||
|
* separated by values.
|
||||||
|
*
|
||||||
|
* BCP-47 2-letter language codes without region.
|
||||||
|
*/
|
||||||
postLanguageHistory: z.array(z.string()),
|
postLanguageHistory: z.array(z.string()),
|
||||||
|
/**
|
||||||
|
* The language for UI translations in the app.
|
||||||
|
*
|
||||||
|
* BCP-47 2-letter language code with or without region,
|
||||||
|
* to match with {@link AppLanguage}.
|
||||||
|
*/
|
||||||
appLanguage: z.string(),
|
appLanguage: z.string(),
|
||||||
}),
|
}),
|
||||||
requireAltTextEnabled: z.boolean(), // should move to server
|
requireAltTextEnabled: z.boolean(), // should move to server
|
||||||
@@ -108,13 +138,17 @@ export const defaults: Schema = {
|
|||||||
lastEmailConfirm: undefined,
|
lastEmailConfirm: undefined,
|
||||||
},
|
},
|
||||||
languagePrefs: {
|
languagePrefs: {
|
||||||
primaryLanguage: deviceLocales[0] || 'en',
|
primaryLanguage: deviceLanguageCodes[0] || 'en',
|
||||||
contentLanguages: deviceLocales || [],
|
contentLanguages: deviceLanguageCodes || [],
|
||||||
postLanguage: deviceLocales[0] || 'en',
|
postLanguage: deviceLanguageCodes[0] || 'en',
|
||||||
postLanguageHistory: (deviceLocales || [])
|
postLanguageHistory: (deviceLanguageCodes || [])
|
||||||
.concat(['en', 'ja', 'pt', 'de'])
|
.concat(['en', 'ja', 'pt', 'de'])
|
||||||
.slice(0, 6),
|
.slice(0, 6),
|
||||||
appLanguage: deviceLocales[0] || 'en',
|
// try full language tag first, then fallback to language code
|
||||||
|
appLanguage: findSupportedAppLanguage([
|
||||||
|
deviceLocales.at(0)?.languageTag,
|
||||||
|
deviceLanguageCodes[0],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
requireAltTextEnabled: false,
|
requireAltTextEnabled: false,
|
||||||
largeAltBadgeEnabled: false,
|
largeAltBadgeEnabled: false,
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import {parse} from 'bcp-47'
|
||||||
|
|
||||||
|
import {dedupArray} from '#/lib/functions'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {Schema} from '#/state/persisted/schema'
|
||||||
|
|
||||||
|
export function normalizeData(data: Schema) {
|
||||||
|
const next = {...data}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize language prefs to ensure that these values only contain 2-letter
|
||||||
|
* country codes without region.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
const langPrefs = {...next.languagePrefs}
|
||||||
|
langPrefs.primaryLanguage = normalizeLanguageTagToTwoLetterCode(
|
||||||
|
langPrefs.primaryLanguage,
|
||||||
|
)
|
||||||
|
langPrefs.contentLanguages = dedupArray(
|
||||||
|
langPrefs.contentLanguages.map(lang =>
|
||||||
|
normalizeLanguageTagToTwoLetterCode(lang),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
langPrefs.postLanguage = langPrefs.postLanguage
|
||||||
|
.split(',')
|
||||||
|
.map(lang => normalizeLanguageTagToTwoLetterCode(lang))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',')
|
||||||
|
langPrefs.postLanguageHistory = dedupArray(
|
||||||
|
langPrefs.postLanguageHistory.map(postLanguage => {
|
||||||
|
return postLanguage
|
||||||
|
.split(',')
|
||||||
|
.map(lang => normalizeLanguageTagToTwoLetterCode(lang))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(',')
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
next.languagePrefs = langPrefs
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`persisted state: failed to normalize language prefs`, {
|
||||||
|
safeMessage: e.message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeLanguageTagToTwoLetterCode(lang: string) {
|
||||||
|
const result = parse(lang).language
|
||||||
|
return result ?? lang
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
|
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
|
||||||
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {isJustAMute} from '#/lib/moderation'
|
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
@@ -113,6 +113,10 @@ function computeSuggestions({
|
|||||||
return items.filter(profile => {
|
return items.filter(profile => {
|
||||||
const modui = moderateProfile(profile, moderationOpts).ui('profileList')
|
const modui = moderateProfile(profile, moderationOpts).ui('profileList')
|
||||||
const isExactMatch = q && profile.handle.toLowerCase() === q
|
const isExactMatch = q && profile.handle.toLowerCase() === q
|
||||||
return isExactMatch || !modui.filter || isJustAMute(modui)
|
return (
|
||||||
|
(isExactMatch && !moduiContainsHideableOffense(modui)) ||
|
||||||
|
!modui.filter ||
|
||||||
|
isJustAMute(modui)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import {QueryClient} from '@tanstack/react-query'
|
import {QueryClient} from '@tanstack/react-query'
|
||||||
import chunk from 'lodash.chunk'
|
import chunk from 'lodash.chunk'
|
||||||
|
|
||||||
|
import {labelIsHideableOffense} from '#/lib/moderation'
|
||||||
import {precacheProfile} from '../profile'
|
import {precacheProfile} from '../profile'
|
||||||
import {FeedNotification, FeedPage, NotificationType} from './types'
|
import {FeedNotification, FeedPage, NotificationType} from './types'
|
||||||
|
|
||||||
@@ -104,6 +105,10 @@ export function shouldFilterNotif(
|
|||||||
notif: AppBskyNotificationListNotifications.Notification,
|
notif: AppBskyNotificationListNotifications.Notification,
|
||||||
moderationOpts: ModerationOpts | undefined,
|
moderationOpts: ModerationOpts | undefined,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
const containsImperative = !!notif.author.labels?.some(labelIsHideableOffense)
|
||||||
|
if (containsImperative) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (!moderationOpts) {
|
if (!moderationOpts) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ jest.mock('jwt-decode', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
jest.mock('expo-localization', () => ({
|
||||||
|
getLocales: () => [],
|
||||||
|
}))
|
||||||
|
|
||||||
describe('session', () => {
|
describe('session', () => {
|
||||||
it('can log in and out', () => {
|
it('can log in and out', () => {
|
||||||
let state = getInitialState([])
|
let state = getInitialState([])
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {device} from '#/storage'
|
||||||
|
|
||||||
|
export const BR_LABELER = 'did:plc:ekitcvx7uwnauoqy5oest3hm'
|
||||||
|
export const ADDITIONAL_LABELERS_MAP: {
|
||||||
|
[countryCode: string]: string[]
|
||||||
|
} = {
|
||||||
|
BR: [BR_LABELER],
|
||||||
|
}
|
||||||
|
export const ALL_ADDITIONAL_LABELERS = Object.values(
|
||||||
|
ADDITIONAL_LABELERS_MAP,
|
||||||
|
).flat()
|
||||||
|
export const NON_CONFIGURABLE_LABELERS = [BR_LABELER]
|
||||||
|
|
||||||
|
export function isNonConfigurableModerationAuthority(did: string) {
|
||||||
|
return NON_CONFIGURABLE_LABELERS.includes(did)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureAdditionalModerationAuthorities() {
|
||||||
|
const geolocation = device.get(['geolocation'])
|
||||||
|
let additionalLabelers: string[] = ALL_ADDITIONAL_LABELERS
|
||||||
|
|
||||||
|
if (geolocation?.countryCode) {
|
||||||
|
additionalLabelers = ADDITIONAL_LABELERS_MAP[geolocation.countryCode] ?? []
|
||||||
|
} else {
|
||||||
|
logger.info(`no geolocation, cannot apply mod authorities`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const appLabelers = Array.from(
|
||||||
|
new Set([...BskyAgent.appLabelers, ...additionalLabelers]),
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(`applying mod authorities`, {
|
||||||
|
additionalLabelers,
|
||||||
|
appLabelers,
|
||||||
|
})
|
||||||
|
|
||||||
|
BskyAgent.configure({appLabelers})
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import {BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
|
import {BSKY_LABELER_DID, BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
import {IS_TEST_USER} from '#/lib/constants'
|
import {IS_TEST_USER} from '#/lib/constants'
|
||||||
|
import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities'
|
||||||
import {readLabelers} from './agent-config'
|
import {readLabelers} from './agent-config'
|
||||||
import {SessionAccount} from './types'
|
import {SessionAccount} from './types'
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ export function configureModerationForGuest() {
|
|||||||
// This global mutation is *only* OK because this code is only relevant for testing.
|
// This global mutation is *only* OK because this code is only relevant for testing.
|
||||||
// Don't add any other global behavior here!
|
// Don't add any other global behavior here!
|
||||||
switchToBskyAppLabeler()
|
switchToBskyAppLabeler()
|
||||||
|
configureAdditionalModerationAuthorities()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureModerationForAccount(
|
export async function configureModerationForAccount(
|
||||||
@@ -31,6 +33,8 @@ export async function configureModerationForAccount(
|
|||||||
// If there are no headers in the storage, we'll not send them on the initial requests.
|
// If there are no headers in the storage, we'll not send them on the initial requests.
|
||||||
// If we wanted to fix this, we could block on the preferences query here.
|
// If we wanted to fix this, we could block on the preferences query here.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureAdditionalModerationAuthorities()
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchToBskyAppLabeler() {
|
function switchToBskyAppLabeler() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {MMKV} from 'react-native-mmkv'
|
import {MMKV} from 'react-native-mmkv'
|
||||||
|
|
||||||
|
import {IS_DEV} from '#/env'
|
||||||
import {Device} from '#/storage/schema'
|
import {Device} from '#/storage/schema'
|
||||||
|
|
||||||
export * from '#/storage/schema'
|
export * from '#/storage/schema'
|
||||||
@@ -71,4 +72,11 @@ export class Storage<Scopes extends unknown[], Schema> {
|
|||||||
*
|
*
|
||||||
* `device.set([key], true)`
|
* `device.set([key], true)`
|
||||||
*/
|
*/
|
||||||
export const device = new Storage<[], Device>({id: 'device'})
|
export const device = new Storage<[], Device>({id: 'bsky_device'})
|
||||||
|
|
||||||
|
if (IS_DEV && typeof window !== 'undefined') {
|
||||||
|
// @ts-ignore
|
||||||
|
window.bsky_storage = {
|
||||||
|
device,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,4 +5,7 @@ export type Device = {
|
|||||||
fontScale: '-2' | '-1' | '0' | '1' | '2'
|
fontScale: '-2' | '-1' | '0' | '1' | '2'
|
||||||
fontFamily: 'system' | 'theme'
|
fontFamily: 'system' | 'theme'
|
||||||
lastNuxDialog: string | undefined
|
lastNuxDialog: string | undefined
|
||||||
|
geolocation?: {
|
||||||
|
countryCode: string | undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {Logotype} from '#/view/icons/Logotype'
|
import {Logotype} from '#/view/icons/Logotype'
|
||||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
|
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
@@ -35,8 +35,7 @@ export const SplashScreen = ({
|
|||||||
<Logotype width={161} fill={t.atoms.text.color} />
|
<Logotype width={161} fill={t.atoms.text.color} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text
|
<Text style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
style={[a.text_md, a.font_semibold, t.atoms.text_contrast_medium]}>
|
|
||||||
<Trans>What's up?</Trans>
|
<Trans>What's up?</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {Logotype} from '#/view/icons/Logotype'
|
import {Logotype} from '#/view/icons/Logotype'
|
||||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
|
import {AppLanguageDropdown} from '#/components/AppLanguageDropdown'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
@@ -78,11 +78,7 @@ export const SplashScreen = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[a.text_md, a.font_bold, t.atoms.text_contrast_medium]}>
|
||||||
a.text_md,
|
|
||||||
a.font_semibold,
|
|
||||||
t.atoms.text_contrast_medium,
|
|
||||||
]}>
|
|
||||||
<Trans>What's up?</Trans>
|
<Trans>What's up?</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {ComposerOptsPostRef} from 'state/shell/composer'
|
|||||||
import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed'
|
import {QuoteEmbed} from 'view/com/util/post-embeds/QuoteEmbed'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
import {PreviewableUserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
|
||||||
export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -122,55 +122,49 @@ function ComposerReplyToImages({
|
|||||||
showFull: boolean
|
showFull: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View style={[styles.imagesContainer, a.mx_xs]}>
|
||||||
style={{
|
|
||||||
width: 65,
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<View style={styles.imagesContainer}>
|
|
||||||
{(images.length === 1 && (
|
{(images.length === 1 && (
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[0].thumb}}
|
source={{uri: images[0].thumb}}
|
||||||
style={styles.singleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
)) ||
|
)) ||
|
||||||
(images.length === 2 && (
|
(images.length === 2 && (
|
||||||
<View style={[styles.imagesInner, styles.imagesRow]}>
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[0].thumb}}
|
source={{uri: images[0].thumb}}
|
||||||
style={styles.doubleImageTall}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[1].thumb}}
|
source={{uri: images[1].thumb}}
|
||||||
style={styles.doubleImageTall}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)) ||
|
)) ||
|
||||||
(images.length === 3 && (
|
(images.length === 3 && (
|
||||||
<View style={[styles.imagesInner, styles.imagesRow]}>
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[0].thumb}}
|
source={{uri: images[0].thumb}}
|
||||||
style={styles.doubleImageTall}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
<View style={styles.imagesInner}>
|
<View style={[a.flex_1, a.gap_2xs]}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[1].thumb}}
|
source={{uri: images[1].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[2].thumb}}
|
source={{uri: images[2].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
@@ -178,31 +172,31 @@ function ComposerReplyToImages({
|
|||||||
</View>
|
</View>
|
||||||
)) ||
|
)) ||
|
||||||
(images.length === 4 && (
|
(images.length === 4 && (
|
||||||
<View style={styles.imagesInner}>
|
<View style={[a.flex_1, a.gap_2xs]}>
|
||||||
<View style={[styles.imagesInner, styles.imagesRow]}>
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[0].thumb}}
|
source={{uri: images[0].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[1].thumb}}
|
source={{uri: images[1].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.imagesInner, styles.imagesRow]}>
|
<View style={[a.flex_1, a.flex_row, a.gap_2xs]}>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[2].thumb}}
|
source={{uri: images[2].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
source={{uri: images[3].thumb}}
|
source={{uri: images[3].thumb}}
|
||||||
style={styles.doubleImage}
|
style={[a.flex_1]}
|
||||||
cachePolicy="memory-disk"
|
cachePolicy="memory-disk"
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
@@ -210,7 +204,6 @@ function ComposerReplyToImages({
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,23 +233,7 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
},
|
height: 64,
|
||||||
imagesInner: {
|
width: 64,
|
||||||
gap: 2,
|
|
||||||
},
|
|
||||||
imagesRow: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
singleImage: {
|
|
||||||
width: 65,
|
|
||||||
height: 65,
|
|
||||||
},
|
|
||||||
doubleImageTall: {
|
|
||||||
width: 32.5,
|
|
||||||
height: 65,
|
|
||||||
},
|
|
||||||
doubleImage: {
|
|
||||||
width: 32.5,
|
|
||||||
height: 32.5,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function AltTextInner({
|
|||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Save`)}
|
label={_(msg`Save`)}
|
||||||
size="medium"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onPress={onPressSubmit}>
|
onPress={onPressSubmit}>
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
|
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {Dimensions} from '#/lib/media/types'
|
||||||
|
import {colors, s} from '#/lib/styles'
|
||||||
|
import {isNative} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {GalleryModel} from '#/state/models/media/gallery'
|
||||||
import {Dimensions} from 'lib/media/types'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {colors, s} from 'lib/styles'
|
|
||||||
import {isNative} from 'platform/detection'
|
|
||||||
import {GalleryModel} from 'state/models/media/gallery'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
|
|
||||||
const IMAGE_GAP = 8
|
const IMAGE_GAP = 8
|
||||||
@@ -263,7 +263,7 @@ const styles = StyleSheet.create({
|
|||||||
altTextControlLabel: {
|
altTextControlLabel: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
},
|
},
|
||||||
altTextHiddenRegion: {
|
altTextHiddenRegion: {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function ThreadgateBtn({
|
|||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="xsmall"
|
size="small"
|
||||||
testID="openReplyGateButton"
|
testID="openReplyGateButton"
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
label={label}
|
label={label}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function SubtitleDialogBtn(props: Props) {
|
|||||||
? _('Opens captions and alt text dialog')
|
? _('Opens captions and alt text dialog')
|
||||||
: _('Opens alt text dialog')
|
: _('Opens alt text dialog')
|
||||||
}
|
}
|
||||||
size="xsmall"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -169,7 +169,7 @@ function SubtitleDialogInner({
|
|||||||
<View style={web([a.flex_row, a.justify_end])}>
|
<View style={web([a.flex_row, a.justify_end])}>
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Done`)}
|
label={_(msg`Done`)}
|
||||||
size={isWeb ? 'small' : 'medium'}
|
size={isWeb ? 'small' : 'large'}
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function SubtitleFilePicker({
|
|||||||
<Button
|
<Button
|
||||||
onPress={handleClick}
|
onPress={handleClick}
|
||||||
label={_('Select subtitle file (.vtt)')}
|
label={_('Select subtitle file (.vtt)')}
|
||||||
size="medium"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
disabled={disabled}>
|
disabled={disabled}>
|
||||||
|
|||||||
@@ -14,22 +14,22 @@ import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {compressIfNeeded} from '#/lib/media/manip'
|
||||||
|
import {cleanError, isNetworkError} from '#/lib/strings/errors'
|
||||||
|
import {enforceLen} from '#/lib/strings/helpers'
|
||||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||||
|
import {colors, gradients, s} from '#/lib/styles'
|
||||||
|
import {useTheme} from '#/lib/ThemeContext'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
useListCreateMutation,
|
useListCreateMutation,
|
||||||
useListMetadataMutation,
|
useListMetadataMutation,
|
||||||
} from '#/state/queries/list'
|
} from '#/state/queries/list'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {compressIfNeeded} from 'lib/media/manip'
|
|
||||||
import {cleanError, isNetworkError} from 'lib/strings/errors'
|
|
||||||
import {enforceLen} from 'lib/strings/helpers'
|
|
||||||
import {colors, gradients, s} from 'lib/styles'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {ErrorMessage} from '../util/error/ErrorMessage'
|
import {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
@@ -359,7 +359,7 @@ export function Component({
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 18,
|
marginBottom: 18,
|
||||||
},
|
},
|
||||||
@@ -373,7 +373,7 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ import {Slider} from '@miblanchard/react-native-slider'
|
|||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import ImageEditor, {Position} from 'react-avatar-editor'
|
import ImageEditor, {Position} from 'react-avatar-editor'
|
||||||
|
|
||||||
|
import {MAX_ALT_TEXT} from '#/lib/constants'
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {enforceLen} from '#/lib/strings/helpers'
|
||||||
|
import {gradients, s} from '#/lib/styles'
|
||||||
|
import {useTheme} from '#/lib/ThemeContext'
|
||||||
|
import {getKeys} from '#/lib/type-assertions'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {MAX_ALT_TEXT} from 'lib/constants'
|
import {GalleryModel} from '#/state/models/media/gallery'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {ImageModel} from '#/state/models/media/image'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {enforceLen} from 'lib/strings/helpers'
|
|
||||||
import {gradients, s} from 'lib/styles'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {getKeys} from 'lib/type-assertions'
|
|
||||||
import {GalleryModel} from 'state/models/media/gallery'
|
|
||||||
import {ImageModel} from 'state/models/media/image'
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {
|
import {
|
||||||
@@ -333,7 +333,7 @@ const styles = StyleSheet.create({
|
|||||||
subsection: {marginTop: 12},
|
subsection: {marginTop: 12},
|
||||||
gap18: {gap: 18},
|
gap18: {gap: 18},
|
||||||
title: {
|
title: {
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
},
|
},
|
||||||
btns: {
|
btns: {
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ import {AppBskyActorDefs} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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 {logger} from '#/logger'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useProfileUpdateMutation} from '#/state/queries/profile'
|
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 {ErrorMessage} from '../util/error/ErrorMessage'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
@@ -261,12 +261,12 @@ export function Component({
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 18,
|
marginBottom: 18,
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
paddingHorizontal: 4,
|
paddingHorizontal: 4,
|
||||||
paddingBottom: 4,
|
paddingBottom: 4,
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
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 {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {s} from '#/lib/styles'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
useOpenLink,
|
useOpenLink,
|
||||||
useSetInAppBrowser,
|
useSetInAppBrowser,
|
||||||
} from '#/state/preferences/in-app-browser'
|
} 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]
|
export const snapPoints = [350]
|
||||||
|
|
||||||
@@ -89,7 +88,7 @@ export function Component({href}: {href: string}) {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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 {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 {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
getMembership,
|
getMembership,
|
||||||
@@ -19,11 +24,6 @@ import {
|
|||||||
useListMembershipRemoveMutation,
|
useListMembershipRemoveMutation,
|
||||||
} from '#/state/queries/list-memberships'
|
} from '#/state/queries/list-memberships'
|
||||||
import {useSession} from '#/state/session'
|
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 {MyLists} from '../lists/MyLists'
|
||||||
import {Button} from '../util/forms/Button'
|
import {Button} from '../util/forms/Button'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
@@ -71,7 +71,7 @@ export function Component({
|
|||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
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 {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 {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
useLanguagePrefs,
|
useLanguagePrefs,
|
||||||
useLanguagePrefsApi,
|
useLanguagePrefsApi,
|
||||||
} from '#/state/preferences/languages'
|
} 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%']
|
export const snapPoints = ['100%']
|
||||||
|
|
||||||
@@ -37,10 +38,10 @@ export function Component({}: {}) {
|
|||||||
langs.sort((a, b) => {
|
langs.sort((a, b) => {
|
||||||
const hasA =
|
const hasA =
|
||||||
langPrefs.contentLanguages.includes(a.code2) ||
|
langPrefs.contentLanguages.includes(a.code2) ||
|
||||||
deviceLocales.includes(a.code2)
|
deviceLanguageCodes.includes(a.code2)
|
||||||
const hasB =
|
const hasB =
|
||||||
langPrefs.contentLanguages.includes(b.code2) ||
|
langPrefs.contentLanguages.includes(b.code2) ||
|
||||||
deviceLocales.includes(b.code2)
|
deviceLanguageCodes.includes(b.code2)
|
||||||
if (hasA === hasB) return a.name.localeCompare(b.name)
|
if (hasA === hasB) return a.name.localeCompare(b.name)
|
||||||
if (hasA) return -1
|
if (hasA) return -1
|
||||||
return 1
|
return 1
|
||||||
@@ -110,7 +111,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
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 {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 {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
|
hasPostLanguage,
|
||||||
useLanguagePrefs,
|
useLanguagePrefs,
|
||||||
useLanguagePrefsApi,
|
useLanguagePrefsApi,
|
||||||
hasPostLanguage,
|
|
||||||
} from '#/state/preferences/languages'
|
} 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%']
|
export const snapPoints = ['100%']
|
||||||
|
|
||||||
@@ -38,10 +39,10 @@ export function Component() {
|
|||||||
langs.sort((a, b) => {
|
langs.sort((a, b) => {
|
||||||
const hasA =
|
const hasA =
|
||||||
hasPostLanguage(langPrefs.postLanguage, a.code2) ||
|
hasPostLanguage(langPrefs.postLanguage, a.code2) ||
|
||||||
deviceLocales.includes(a.code2)
|
deviceLanguageCodes.includes(a.code2)
|
||||||
const hasB =
|
const hasB =
|
||||||
hasPostLanguage(langPrefs.postLanguage, b.code2) ||
|
hasPostLanguage(langPrefs.postLanguage, b.code2) ||
|
||||||
deviceLocales.includes(b.code2)
|
deviceLanguageCodes.includes(b.code2)
|
||||||
if (hasA === hasB) return a.name.localeCompare(b.name)
|
if (hasA === hasB) return a.name.localeCompare(b.name)
|
||||||
if (hasA) return -1
|
if (hasA) return -1
|
||||||
return 1
|
return 1
|
||||||
@@ -118,7 +119,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: 'bold',
|
fontWeight: '600',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user