Merge remote-tracking branch 'origin/main' into samuel/alf-login
This commit is contained in:
@@ -64,7 +64,7 @@ describe('Curate lists', () => {
|
||||
await element(by.text('Edit list details')).tap()
|
||||
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
|
||||
await element(by.id('changeAvatarBtn')).tap()
|
||||
await element(by.text('Library')).tap()
|
||||
await element(by.text('Upload from Library')).tap()
|
||||
await sleep(3e3)
|
||||
await element(by.id('saveBtn')).tap()
|
||||
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
|
||||
@@ -81,7 +81,7 @@ describe('Curate lists', () => {
|
||||
await element(by.text('Edit list details')).tap()
|
||||
await expect(element(by.id('createOrEditListModal'))).toBeVisible()
|
||||
await element(by.id('changeAvatarBtn')).tap()
|
||||
await element(by.text('Remove')).tap()
|
||||
await element(by.text('Remove Avatar')).tap()
|
||||
await element(by.id('saveBtn')).tap()
|
||||
await expect(element(by.id('createOrEditListModal'))).not.toBeVisible()
|
||||
await expect(element(by.id('userAvatarFallback'))).toExist()
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('Home screen', () => {
|
||||
|
||||
it('Can go to feeds page using feeds button in tab bar', async () => {
|
||||
await element(by.id('homeScreenFeedTabs-Feeds ✨')).tap()
|
||||
await expect(element(by.text('Discover new feeds'))).toBeVisible()
|
||||
await expect(element(by.text('Discover New Feeds'))).toBeVisible()
|
||||
})
|
||||
|
||||
it('Feeds button disappears after pinning a feed', async () => {
|
||||
|
||||
@@ -70,10 +70,10 @@ describe('Profile screen', () => {
|
||||
await element(by.id('profileHeaderEditProfileButton')).tap()
|
||||
await expect(element(by.id('editProfileModal'))).toBeVisible()
|
||||
await element(by.id('changeBannerBtn')).tap()
|
||||
await element(by.text('Library')).tap()
|
||||
await element(by.text('Upload from Library')).tap()
|
||||
await sleep(3e3)
|
||||
await element(by.id('changeAvatarBtn')).tap()
|
||||
await element(by.text('Library')).tap()
|
||||
await element(by.text('Upload from Library')).tap()
|
||||
await sleep(3e3)
|
||||
await element(by.id('editProfileSaveBtn')).tap()
|
||||
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
|
||||
@@ -87,9 +87,9 @@ describe('Profile screen', () => {
|
||||
await element(by.id('profileHeaderEditProfileButton')).tap()
|
||||
await expect(element(by.id('editProfileModal'))).toBeVisible()
|
||||
await element(by.id('changeBannerBtn')).tap()
|
||||
await element(by.text('Remove')).tap()
|
||||
await element(by.text('Remove Banner')).tap()
|
||||
await element(by.id('changeAvatarBtn')).tap()
|
||||
await element(by.text('Remove')).tap()
|
||||
await element(by.text('Remove Avatar')).tap()
|
||||
await element(by.id('editProfileSaveBtn')).tap()
|
||||
await expect(element(by.id('editProfileModal'))).not.toBeVisible()
|
||||
await expect(element(by.id('userBannerFallback'))).toExist()
|
||||
|
||||
+10
-8
@@ -12,6 +12,7 @@ describe('Create account', () => {
|
||||
})
|
||||
|
||||
it('I can create a new account with text verification', async () => {
|
||||
console.log('SERVICE IS', service)
|
||||
await element(by.id('e2eOpenLoggedOutView')).tap()
|
||||
|
||||
await element(by.id('createAccountButton')).tap()
|
||||
@@ -28,16 +29,17 @@ describe('Create account', () => {
|
||||
await device.takeScreenshot('4- entered account details')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('phoneInput')).typeText('8042221111')
|
||||
await element(by.id('requestCodeBtn')).tap()
|
||||
await device.takeScreenshot('5- requested code')
|
||||
|
||||
await element(by.id('codeInput')).typeText('000000')
|
||||
await device.takeScreenshot('6- entered code')
|
||||
await element(by.id('handleInput')).typeText('text-verification-test')
|
||||
await device.takeScreenshot('5- entered handle')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('handleInput')).typeText('text-verification-test')
|
||||
await device.takeScreenshot('7- entered handle')
|
||||
await element(by.id('phoneInput')).typeText('8042221111')
|
||||
await element(by.id('requestCodeBtn')).tap()
|
||||
await device.takeScreenshot('6- requested code')
|
||||
|
||||
await element(by.id('codeInput')).typeText('000000')
|
||||
await device.takeScreenshot('7- entered code')
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
await element(by.id('nextBtn')).tap()
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
html, body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Buttons and inputs have a font set by UA, so we'll have to reset that */
|
||||
@@ -141,7 +141,7 @@
|
||||
|
||||
/* ProseMirror */
|
||||
.ProseMirror {
|
||||
font: 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font: 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif;
|
||||
min-height: 140px;
|
||||
}
|
||||
.ProseMirror-dark {
|
||||
|
||||
+33
-3
@@ -72,6 +72,7 @@ export async function createServer(
|
||||
const phoneParams = phoneRequired
|
||||
? {
|
||||
phoneVerificationRequired: true,
|
||||
phoneVerificationProvider: 'twilio',
|
||||
twilioAccountSid: 'ACXXXXXXX',
|
||||
twilioAuthToken: 'AUTH',
|
||||
twilioServiceSid: 'VAXXXXXXXX',
|
||||
@@ -95,6 +96,35 @@ export async function createServer(
|
||||
})
|
||||
mockTwilio(testNet.pds)
|
||||
|
||||
// add the test mod authority
|
||||
if (!phoneRequired) {
|
||||
const agent = new BskyAgent({service: pdsUrl})
|
||||
const res = await agent.api.com.atproto.server.createAccount({
|
||||
email: 'mod-authority@test.com',
|
||||
handle: 'mod-authority.test',
|
||||
password: 'hunter2',
|
||||
})
|
||||
agent.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`)
|
||||
await agent.api.app.bsky.actor.profile.create(
|
||||
{repo: res.data.did},
|
||||
{
|
||||
displayName: 'Dev-env Moderation',
|
||||
description: `The pretend version of mod.bsky.app`,
|
||||
},
|
||||
)
|
||||
|
||||
await agent.api.app.bsky.labeler.service.create(
|
||||
{repo: res.data.did, rkey: 'self'},
|
||||
{
|
||||
policies: {
|
||||
labelValues: ['!hide', '!warn'],
|
||||
labelValueDefinitions: [],
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const pic = fs.readFileSync(
|
||||
path.join(__dirname, '..', 'assets', 'default-avatar.png'),
|
||||
)
|
||||
@@ -455,13 +485,13 @@ async function getPort(start = 3000) {
|
||||
}
|
||||
|
||||
export const mockTwilio = (pds: TestPds) => {
|
||||
if (!pds.ctx.twilio) return
|
||||
if (!pds.ctx.phoneVerifier) return
|
||||
|
||||
pds.ctx.twilio.sendCode = async (_number: string) => {
|
||||
pds.ctx.phoneVerifier.sendCode = async (_number: string) => {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
pds.ctx.twilio.verifyCode = async (_number: string, code: string) => {
|
||||
pds.ctx.phoneVerifier.verifyCode = async (_number: string, code: string) => {
|
||||
return code === '000000'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ module.exports = {
|
||||
'en',
|
||||
'de',
|
||||
'es',
|
||||
'fi',
|
||||
'fr',
|
||||
'hi',
|
||||
'id',
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
"make-deploy-bundle": "bash scripts/bundleUpdate.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.12.1",
|
||||
"@atproto/api": "^0.12.2",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
|
||||
+5
-1
@@ -565,7 +565,11 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||
}
|
||||
|
||||
function getCurrentRouteName() {
|
||||
return navigationRef.getCurrentRoute()?.name
|
||||
if (navigationRef.isReady()) {
|
||||
return navigationRef.getCurrentRoute()?.name
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -206,12 +206,16 @@ export function Inner({children, style}: DialogInnerProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ScrollableInner({children, style}: DialogInnerProps) {
|
||||
export function ScrollableInner({
|
||||
children,
|
||||
keyboardDismissMode,
|
||||
style,
|
||||
}: DialogInnerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
return (
|
||||
<BottomSheetScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardDismissMode={keyboardDismissMode || 'on-drag'}
|
||||
style={[
|
||||
a.flex_1, // main diff is this
|
||||
a.p_xl,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react'
|
||||
import type {AccessibilityProps, GestureResponderEvent} from 'react-native'
|
||||
import type {
|
||||
AccessibilityProps,
|
||||
GestureResponderEvent,
|
||||
ScrollViewProps,
|
||||
} from 'react-native'
|
||||
import {BottomSheetProps} from '@gorhom/bottom-sheet'
|
||||
|
||||
import {ViewStyleProp} from '#/alf'
|
||||
@@ -61,9 +65,11 @@ export type DialogInnerProps =
|
||||
label?: undefined
|
||||
accessibilityLabelledBy: A11yProps['aria-labelledby']
|
||||
accessibilityDescribedBy: string
|
||||
keyboardDismissMode?: ScrollViewProps['keyboardDismissMode']
|
||||
}>
|
||||
| DialogInnerPropsBase<{
|
||||
label: string
|
||||
accessibilityLabelledBy?: undefined
|
||||
accessibilityDescribedBy?: undefined
|
||||
keyboardDismissMode?: ScrollViewProps['keyboardDismissMode']
|
||||
}>
|
||||
|
||||
@@ -37,7 +37,9 @@ function ReportDialogInner(props: ReportDialogProps) {
|
||||
const isLoading = useDelayedLoading(500, isLabelerLoading)
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner label="Report Dialog">
|
||||
<Dialog.ScrollableInner
|
||||
label="Report Dialog"
|
||||
keyboardDismissMode="interactive">
|
||||
{isLoading ? (
|
||||
<View style={[a.align_center, {height: 100}]}>
|
||||
<Loader size="xl" />
|
||||
|
||||
@@ -87,7 +87,7 @@ export function TagMenu({
|
||||
author: authorHandle,
|
||||
})
|
||||
},
|
||||
testID: 'tagMenuSeachByUser',
|
||||
testID: 'tagMenuSearchByUser',
|
||||
icon: {
|
||||
ios: {
|
||||
name: 'magnifyingglass',
|
||||
|
||||
@@ -56,7 +56,8 @@ export function ScreenHider({
|
||||
|
||||
const isNoPwi = !!modui.blurs.find(
|
||||
cause =>
|
||||
cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated',
|
||||
cause.type === 'label' &&
|
||||
cause.labelDef.identifier === '!no-unauthenticated',
|
||||
)
|
||||
return (
|
||||
<CenteredView
|
||||
|
||||
@@ -6,6 +6,7 @@ import {useSessionApi, SessionAccount} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {LogEvents} from '../statsig/statsig'
|
||||
|
||||
export function useAccountSwitcher() {
|
||||
const {track} = useAnalytics()
|
||||
@@ -14,7 +15,10 @@ export function useAccountSwitcher() {
|
||||
const {requestSwitchToAccount} = useLoggedOutViewControls()
|
||||
|
||||
const onPressSwitchAccount = useCallback(
|
||||
async (account: SessionAccount) => {
|
||||
async (
|
||||
account: SessionAccount,
|
||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||
) => {
|
||||
track('Settings:SwitchAccountButtonClicked')
|
||||
|
||||
try {
|
||||
@@ -28,7 +32,7 @@ export function useAccountSwitcher() {
|
||||
// So we change the URL ourselves. The navigator will pick it up on remount.
|
||||
history.pushState(null, '', '/')
|
||||
}
|
||||
await selectAccount(account)
|
||||
await selectAccount(account, logContext)
|
||||
setTimeout(() => {
|
||||
Toast.show(`Signed in as @${account.handle}`)
|
||||
}, 100)
|
||||
|
||||
@@ -6,6 +6,7 @@ let refCount = 0
|
||||
function incrementRefCount() {
|
||||
if (refCount === 0) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
document.documentElement.style.scrollbarGutter = 'auto'
|
||||
}
|
||||
refCount++
|
||||
}
|
||||
@@ -14,6 +15,7 @@ function decrementRefCount() {
|
||||
refCount--
|
||||
if (refCount === 0) {
|
||||
document.body.style.overflow = ''
|
||||
document.documentElement.style.scrollbarGutter = ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import RNFS from 'react-native-fs'
|
||||
import {CropperOptions} from './types'
|
||||
import {compressIfNeeded} from './manip'
|
||||
|
||||
let _imageCounter = 0
|
||||
async function getFile() {
|
||||
let files = await RNFS.readDir(
|
||||
RNFS.LibraryDirectoryPath.split('/')
|
||||
@@ -12,7 +11,7 @@ async function getFile() {
|
||||
.join('/'),
|
||||
)
|
||||
files = files.filter(file => file.path.endsWith('.JPG'))
|
||||
const file = files[_imageCounter++ % files.length]
|
||||
const file = files[0]
|
||||
return await compressIfNeeded({
|
||||
path: file.path,
|
||||
mime: 'image/jpeg',
|
||||
|
||||
@@ -118,11 +118,15 @@ export function useModerationCauseDescription(
|
||||
(labeler?.creator.handle ? '@' + labeler?.creator.handle : undefined)
|
||||
if (!source) {
|
||||
if (cause.label.src === BSKY_LABELER_DID) {
|
||||
source = 'Bluesky Moderation'
|
||||
source = 'Bluesky Moderation Service'
|
||||
} else {
|
||||
source = cause.label.src
|
||||
}
|
||||
}
|
||||
if (def.identifier === 'porn' || def.identifier === 'sexual') {
|
||||
strings.name = 'Adult Content'
|
||||
}
|
||||
|
||||
return {
|
||||
icon:
|
||||
def.identifier === '!no-unauthenticated'
|
||||
|
||||
@@ -7,6 +7,7 @@ import {logger} from '#/logger'
|
||||
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {SessionAccount, getAgent} from '#/state/session'
|
||||
import {logEvent} from '../statsig/statsig'
|
||||
|
||||
const SERVICE_DID = (serviceUrl?: string) =>
|
||||
serviceUrl?.includes('staging')
|
||||
@@ -123,6 +124,7 @@ export function init(queryClient: QueryClient) {
|
||||
logger.DebugContext.notifications,
|
||||
)
|
||||
track('Notificatons:OpenApp')
|
||||
logEvent('notifications:openApp', {})
|
||||
truncateAndInvalidate(queryClient, RQKEY_NOTIFS())
|
||||
resetToTab('NotificationsTab') // open notifications tab
|
||||
}
|
||||
|
||||
@@ -2,10 +2,26 @@ export type LogEvents = {
|
||||
init: {
|
||||
initMs: number
|
||||
}
|
||||
'account:loggedIn': {
|
||||
logContext: 'LoginForm' | 'SwitchAccount' | 'ChooseAccountForm' | 'Settings'
|
||||
withPassword: boolean
|
||||
}
|
||||
'account:loggedOut': {
|
||||
logContext: 'SwitchAccount' | 'Settings' | 'Deactivated'
|
||||
}
|
||||
'notifications:openApp': {}
|
||||
'state:background': {
|
||||
secondsActive: number
|
||||
}
|
||||
'state:foreground': {}
|
||||
'feed:endReached': {
|
||||
feedType: string
|
||||
itemCount: number
|
||||
}
|
||||
'feed:refresh': {
|
||||
feedType: string
|
||||
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
|
||||
}
|
||||
'post:create': {
|
||||
imageCount: number
|
||||
isReply: boolean
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react'
|
||||
import {Platform} from 'react-native'
|
||||
import {
|
||||
Statsig,
|
||||
StatsigProvider,
|
||||
useGate as useStatsigGate,
|
||||
} from 'statsig-react-native-expo'
|
||||
import {AppState, AppStateStatus} from 'react-native'
|
||||
import {useSession} from '../../state/session'
|
||||
import {sha256} from 'js-sha256'
|
||||
import {LogEvents} from './events'
|
||||
@@ -58,9 +60,34 @@ function toStatsigUser(did: string | undefined) {
|
||||
if (did) {
|
||||
userID = sha256(did)
|
||||
}
|
||||
return {userID}
|
||||
return {
|
||||
userID,
|
||||
platform: Platform.OS,
|
||||
}
|
||||
}
|
||||
|
||||
let lastState: AppStateStatus = AppState.currentState
|
||||
let lastActive = lastState === 'active' ? performance.now() : null
|
||||
AppState.addEventListener('change', (state: AppStateStatus) => {
|
||||
if (state === lastState) {
|
||||
return
|
||||
}
|
||||
lastState = state
|
||||
if (state === 'active') {
|
||||
lastActive = performance.now()
|
||||
logEvent('state:foreground', {})
|
||||
} else {
|
||||
let secondsActive = 0
|
||||
if (lastActive != null) {
|
||||
secondsActive = Math.round((performance.now() - lastActive) / 1e3)
|
||||
}
|
||||
lastActive = null
|
||||
logEvent('state:background', {
|
||||
secondsActive,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export function Provider({children}: {children: React.ReactNode}) {
|
||||
const {currentAccount} = useSession()
|
||||
const currentStatsigUser = React.useMemo(
|
||||
|
||||
@@ -122,6 +122,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
|
||||
return AppLanguage.de
|
||||
case 'es':
|
||||
return AppLanguage.es
|
||||
case 'fi':
|
||||
return AppLanguage.fi
|
||||
case 'fr':
|
||||
return AppLanguage.fr
|
||||
case 'hi':
|
||||
|
||||
@@ -6,6 +6,7 @@ import {messages as messagesEn} from '#/locale/locales/en/messages'
|
||||
import {messages as messagesDe} from '#/locale/locales/de/messages'
|
||||
import {messages as messagesId} from '#/locale/locales/id/messages'
|
||||
import {messages as messagesEs} from '#/locale/locales/es/messages'
|
||||
import {messages as messagesFi} from '#/locale/locales/fi/messages'
|
||||
import {messages as messagesFr} from '#/locale/locales/fr/messages'
|
||||
import {messages as messagesHi} from '#/locale/locales/hi/messages'
|
||||
import {messages as messagesJa} from '#/locale/locales/ja/messages'
|
||||
@@ -32,6 +33,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
i18n.loadAndActivate({locale, messages: messagesEs})
|
||||
break
|
||||
}
|
||||
case AppLanguage.fi: {
|
||||
i18n.loadAndActivate({locale, messages: messagesFi})
|
||||
break
|
||||
}
|
||||
case AppLanguage.fr: {
|
||||
i18n.loadAndActivate({locale, messages: messagesFr})
|
||||
break
|
||||
|
||||
@@ -20,6 +20,10 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
mod = await import(`./locales/es/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.fi: {
|
||||
mod = await import(`./locales/fi/messages`)
|
||||
break
|
||||
}
|
||||
case AppLanguage.fr: {
|
||||
mod = await import(`./locales/fr/messages`)
|
||||
break
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum AppLanguage {
|
||||
en = 'en',
|
||||
de = 'de',
|
||||
es = 'es',
|
||||
fi = 'fi',
|
||||
fr = 'fr',
|
||||
hi = 'hi',
|
||||
id = 'id',
|
||||
@@ -29,6 +30,7 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
|
||||
{code2: AppLanguage.en, name: 'English'},
|
||||
{code2: AppLanguage.de, name: 'Deutsch – German'},
|
||||
{code2: AppLanguage.es, name: 'Español – Spanish'},
|
||||
{code2: AppLanguage.fi, name: 'Suomi – Finnish'},
|
||||
{code2: AppLanguage.fr, name: 'Français – French'},
|
||||
{code2: AppLanguage.hi, name: 'हिंदी – Hindi'},
|
||||
{code2: AppLanguage.id, name: 'Bahasa Indonesia – Indonesian'},
|
||||
|
||||
+1859
-783
File diff suppressed because it is too large
Load Diff
+1861
-781
File diff suppressed because it is too large
Load Diff
+1841
-761
File diff suppressed because it is too large
Load Diff
+1851
-771
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1861
-781
File diff suppressed because it is too large
Load Diff
+1849
-769
File diff suppressed because it is too large
Load Diff
+1857
-780
File diff suppressed because it is too large
Load Diff
+2011
-950
File diff suppressed because it is too large
Load Diff
+1859
-783
File diff suppressed because it is too large
Load Diff
+748
-408
File diff suppressed because it is too large
Load Diff
+1860
-780
File diff suppressed because it is too large
Load Diff
+2310
-1521
File diff suppressed because it is too large
Load Diff
+1862
-782
File diff suppressed because it is too large
Load Diff
@@ -147,7 +147,7 @@ export function Deactivated() {
|
||||
variant="ghost"
|
||||
size="large"
|
||||
label={_(msg`Log out`)}
|
||||
onPress={logout}>
|
||||
onPress={() => logout('Deactivated')}>
|
||||
<ButtonText style={[{color: t.palette.primary_500}]}>
|
||||
<Trans>Log out</Trans>
|
||||
</ButtonText>
|
||||
@@ -176,7 +176,7 @@ export function Deactivated() {
|
||||
variant="ghost"
|
||||
size="large"
|
||||
label={_(msg`Log out`)}
|
||||
onPress={logout}>
|
||||
onPress={() => logout('Deactivated')}>
|
||||
<ButtonText style={[{color: t.palette.primary_500}]}>
|
||||
<Trans>Log out</Trans>
|
||||
</ButtonText>
|
||||
|
||||
@@ -95,11 +95,14 @@ export const LoginForm = ({
|
||||
}
|
||||
|
||||
// TODO remove double login
|
||||
await login({
|
||||
service: serviceUrl,
|
||||
identifier: fullIdent,
|
||||
password,
|
||||
})
|
||||
await login(
|
||||
{
|
||||
service: serviceUrl,
|
||||
identifier: fullIdent,
|
||||
password,
|
||||
},
|
||||
'LoginForm',
|
||||
)
|
||||
} catch (e: any) {
|
||||
const errMsg = e.toString()
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
|
||||
+38
-23
@@ -20,6 +20,7 @@ import {useCloseAllActiveElements} from '#/state/util'
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {hasProp} from '#/lib/type-guards'
|
||||
import {readLabelers} from './agent-config'
|
||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
||||
|
||||
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
|
||||
|
||||
@@ -54,17 +55,22 @@ export type ApiContext = {
|
||||
verificationPhone?: string
|
||||
verificationCode?: string
|
||||
}) => Promise<void>
|
||||
login: (props: {
|
||||
service: string
|
||||
identifier: string
|
||||
password: string
|
||||
}) => Promise<void>
|
||||
login: (
|
||||
props: {
|
||||
service: string
|
||||
identifier: string
|
||||
password: string
|
||||
},
|
||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||
) => Promise<void>
|
||||
/**
|
||||
* A full logout. Clears the `currentAccount` from session, AND removes
|
||||
* access tokens from all accounts, so that returning as any user will
|
||||
* require a full login.
|
||||
*/
|
||||
logout: () => Promise<void>
|
||||
logout: (
|
||||
logContext: LogEvents['account:loggedOut']['logContext'],
|
||||
) => Promise<void>
|
||||
/**
|
||||
* A partial logout. Clears the `currentAccount` from session, but DOES NOT
|
||||
* clear access tokens from accounts, allowing the user to return to their
|
||||
@@ -76,7 +82,10 @@ export type ApiContext = {
|
||||
initSession: (account: SessionAccount) => Promise<void>
|
||||
resumeSession: (account?: SessionAccount) => Promise<void>
|
||||
removeAccount: (account: SessionAccount) => void
|
||||
selectAccount: (account: SessionAccount) => Promise<void>
|
||||
selectAccount: (
|
||||
account: SessionAccount,
|
||||
logContext: LogEvents['account:loggedIn']['logContext'],
|
||||
) => Promise<void>
|
||||
updateCurrentAccount: (
|
||||
account: Partial<
|
||||
Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'>
|
||||
@@ -286,7 +295,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
)
|
||||
|
||||
const login = React.useCallback<ApiContext['login']>(
|
||||
async ({service, identifier, password}) => {
|
||||
async ({service, identifier, password}, logContext) => {
|
||||
logger.debug(`session: login`, {}, logger.DebugContext.session)
|
||||
|
||||
const agent = new BskyAgent({service})
|
||||
@@ -329,24 +338,29 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
logger.debug(`session: logged in`, {}, logger.DebugContext.session)
|
||||
|
||||
track('Sign In', {resumedSession: false})
|
||||
logEvent('account:loggedIn', {logContext, withPassword: true})
|
||||
},
|
||||
[upsertAccount, queryClient, clearCurrentAccount],
|
||||
)
|
||||
|
||||
const logout = React.useCallback<ApiContext['logout']>(async () => {
|
||||
logger.debug(`session: logout`)
|
||||
clearCurrentAccount()
|
||||
setStateAndPersist(s => {
|
||||
return {
|
||||
...s,
|
||||
accounts: s.accounts.map(a => ({
|
||||
...a,
|
||||
refreshJwt: undefined,
|
||||
accessJwt: undefined,
|
||||
})),
|
||||
}
|
||||
})
|
||||
}, [clearCurrentAccount, setStateAndPersist])
|
||||
const logout = React.useCallback<ApiContext['logout']>(
|
||||
async logContext => {
|
||||
logger.debug(`session: logout`)
|
||||
clearCurrentAccount()
|
||||
setStateAndPersist(s => {
|
||||
return {
|
||||
...s,
|
||||
accounts: s.accounts.map(a => ({
|
||||
...a,
|
||||
refreshJwt: undefined,
|
||||
accessJwt: undefined,
|
||||
})),
|
||||
}
|
||||
})
|
||||
logEvent('account:loggedOut', {logContext})
|
||||
},
|
||||
[clearCurrentAccount, setStateAndPersist],
|
||||
)
|
||||
|
||||
const initSession = React.useCallback<ApiContext['initSession']>(
|
||||
async account => {
|
||||
@@ -540,11 +554,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||
)
|
||||
|
||||
const selectAccount = React.useCallback<ApiContext['selectAccount']>(
|
||||
async account => {
|
||||
async (account, logContext) => {
|
||||
setState(s => ({...s, isSwitchingAccounts: true}))
|
||||
try {
|
||||
await initSession(account)
|
||||
setState(s => ({...s, isSwitchingAccounts: false}))
|
||||
logEvent('account:loggedIn', {logContext, withPassword: false})
|
||||
} catch (e) {
|
||||
// reset this in case of error
|
||||
setState(s => ({...s, isSwitchingAccounts: false}))
|
||||
|
||||
@@ -447,7 +447,7 @@ export const ComposePost = observer(function ComposePost({
|
||||
/>
|
||||
)}
|
||||
{quote ? (
|
||||
<View style={[s.mt5, isWeb && s.mb10]}>
|
||||
<View style={[s.mt5, isWeb && s.mb10, {pointerEvents: 'none'}]}>
|
||||
<QuoteEmbed quote={quote} />
|
||||
</View>
|
||||
) : undefined}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import {useState, useEffect} from 'react'
|
||||
import * as apilib from 'lib/api/index'
|
||||
import {getLinkMeta} from 'lib/link-meta/link-meta'
|
||||
import {ComposerOpts} from 'state/shell/composer'
|
||||
import {getAgent} from '#/state/session'
|
||||
|
||||
export function useExternalLinkFetch({}: {
|
||||
setQuote: (opts: ComposerOpts['quote']) => void
|
||||
}) {
|
||||
const [extLink, setExtLink] = useState<apilib.ExternalEmbedDraft | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let aborted = false
|
||||
const cleanup = () => {
|
||||
aborted = true
|
||||
}
|
||||
if (!extLink) {
|
||||
return cleanup
|
||||
}
|
||||
if (!extLink.meta) {
|
||||
getLinkMeta(getAgent(), extLink.uri).then(meta => {
|
||||
if (aborted) {
|
||||
return
|
||||
}
|
||||
setExtLink({
|
||||
uri: extLink.uri,
|
||||
isLoading: !!meta.image,
|
||||
meta,
|
||||
})
|
||||
})
|
||||
return cleanup
|
||||
}
|
||||
if (extLink.isLoading) {
|
||||
setExtLink({
|
||||
...extLink,
|
||||
isLoading: false, // done
|
||||
})
|
||||
}
|
||||
return cleanup
|
||||
}, [extLink])
|
||||
|
||||
return {extLink, setExtLink}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {listenSoftReset} from '#/state/events'
|
||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||
import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
|
||||
const POLL_FREQ = 60e3 // 60sec
|
||||
|
||||
@@ -68,6 +69,10 @@ export function FeedPage({
|
||||
scrollToTop()
|
||||
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
|
||||
setHasNew(false)
|
||||
logEvent('feed:refresh', {
|
||||
feedType: feed.split('|')[0],
|
||||
reason: 'soft-reset',
|
||||
})
|
||||
}
|
||||
}, [navigation, isPageFocused, scrollToTop, queryClient, feed, setHasNew])
|
||||
|
||||
@@ -89,6 +94,10 @@ export function FeedPage({
|
||||
scrollToTop()
|
||||
truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
|
||||
setHasNew(false)
|
||||
logEvent('feed:refresh', {
|
||||
feedType: feed.split('|')[0],
|
||||
reason: 'load-latest',
|
||||
})
|
||||
}, [scrollToTop, feed, queryClient, setHasNew])
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,13 @@
|
||||
*
|
||||
*/
|
||||
import React from 'react'
|
||||
import {SafeAreaView, Text, TouchableOpacity, StyleSheet} from 'react-native'
|
||||
import {
|
||||
SafeAreaView,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -23,14 +29,14 @@ const ImageDefaultHeader = ({onRequestClose}: Props) => {
|
||||
return (
|
||||
<SafeAreaView style={styles.root}>
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
style={[styles.closeButton, styles.blurredBackground]}
|
||||
onPress={onRequestClose}
|
||||
hitSlop={HIT_SLOP}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Close image`)}
|
||||
accessibilityHint={_(msg`Closes viewer for header image`)}
|
||||
onAccessibilityEscape={onRequestClose}>
|
||||
<Text style={styles.closeText}>✕</Text>
|
||||
<FontAwesomeIcon icon="close" color={'#fff'} size={22} />
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
)
|
||||
@@ -42,8 +48,8 @@ const styles = StyleSheet.create({
|
||||
pointerEvents: 'box-none',
|
||||
},
|
||||
closeButton: {
|
||||
marginRight: 8,
|
||||
marginTop: 8,
|
||||
marginRight: 10,
|
||||
marginTop: 10,
|
||||
width: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
@@ -51,13 +57,10 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 22,
|
||||
backgroundColor: '#00000077',
|
||||
},
|
||||
closeText: {
|
||||
lineHeight: 22,
|
||||
fontSize: 19,
|
||||
textAlign: 'center',
|
||||
color: '#FFF',
|
||||
includeFontPadding: false,
|
||||
},
|
||||
blurredBackground: {
|
||||
backdropFilter: 'blur(10px)',
|
||||
WebkitBackdropFilter: 'blur(10px)',
|
||||
} as ViewStyle,
|
||||
})
|
||||
|
||||
export default ImageDefaultHeader
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Pressable,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
ProfileImageLightbox,
|
||||
} from '#/state/lightbox'
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
|
||||
interface Img {
|
||||
uri: string
|
||||
@@ -111,6 +113,14 @@ function LightboxInner({
|
||||
return () => window.removeEventListener('keydown', onKeyDown)
|
||||
}, [onKeyDown])
|
||||
|
||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
||||
const btnStyle = React.useMemo(() => {
|
||||
return isTabletOrDesktop ? styles.btnTablet : styles.btnMobile
|
||||
}, [isTabletOrDesktop])
|
||||
const iconSize = React.useMemo(() => {
|
||||
return isTabletOrDesktop ? 32 : 24
|
||||
}, [isTabletOrDesktop])
|
||||
|
||||
return (
|
||||
<View style={styles.mask}>
|
||||
<TouchableWithoutFeedback
|
||||
@@ -130,28 +140,38 @@ function LightboxInner({
|
||||
{canGoLeft && (
|
||||
<TouchableOpacity
|
||||
onPress={onPressLeft}
|
||||
style={[styles.btn, styles.leftBtn]}
|
||||
style={[
|
||||
styles.btn,
|
||||
btnStyle,
|
||||
styles.leftBtn,
|
||||
styles.blurredBackground,
|
||||
]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Previous image`)}
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
icon="angle-left"
|
||||
style={styles.icon as FontAwesomeIconStyle}
|
||||
size={40}
|
||||
size={iconSize}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{canGoRight && (
|
||||
<TouchableOpacity
|
||||
onPress={onPressRight}
|
||||
style={[styles.btn, styles.rightBtn]}
|
||||
style={[
|
||||
styles.btn,
|
||||
btnStyle,
|
||||
styles.rightBtn,
|
||||
styles.blurredBackground,
|
||||
]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Next image`)}
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
icon="angle-right"
|
||||
style={styles.icon as FontAwesomeIconStyle}
|
||||
size={40}
|
||||
size={iconSize}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
@@ -213,20 +233,30 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
btn: {
|
||||
position: 'absolute',
|
||||
backgroundColor: '#000',
|
||||
width: 50,
|
||||
height: 50,
|
||||
backgroundColor: '#00000077',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
btnTablet: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
left: 30,
|
||||
right: 30,
|
||||
},
|
||||
btnMobile: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
left: 20,
|
||||
right: 20,
|
||||
},
|
||||
leftBtn: {
|
||||
left: 30,
|
||||
right: 'auto',
|
||||
top: '50%',
|
||||
},
|
||||
rightBtn: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
left: 'auto',
|
||||
top: '50%',
|
||||
},
|
||||
footer: {
|
||||
@@ -234,4 +264,8 @@ const styles = StyleSheet.create({
|
||||
paddingVertical: 24,
|
||||
backgroundColor: colors.black,
|
||||
},
|
||||
blurredBackground: {
|
||||
backdropFilter: 'blur(10px)',
|
||||
WebkitBackdropFilter: 'blur(10px)',
|
||||
} as ViewStyle,
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
|
||||
track('Settings:SignOutButtonClicked')
|
||||
closeAllActiveElements()
|
||||
// needs to be in timeout or the modal re-opens
|
||||
setTimeout(() => logout(), 0)
|
||||
setTimeout(() => logout('SwitchAccount'), 0)
|
||||
}, [track, logout, closeAllActiveElements])
|
||||
|
||||
const contents = (
|
||||
@@ -95,7 +95,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
|
||||
key={account.did}
|
||||
style={[isSwitchingAccounts && styles.dimmed]}
|
||||
onPress={
|
||||
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
|
||||
isSwitchingAccounts
|
||||
? undefined
|
||||
: () => onPressSwitchAccount(account, 'SwitchAccount')
|
||||
}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Switch to ${account.handle}`)}
|
||||
|
||||
@@ -108,7 +108,8 @@ export function PostThread({
|
||||
?.ui('contentList')
|
||||
.blurs.find(
|
||||
cause =>
|
||||
cause.type === 'label' && cause.labelDef.id === '!no-unauthenticated',
|
||||
cause.type === 'label' &&
|
||||
cause.labelDef.identifier === '!no-unauthenticated',
|
||||
)
|
||||
}, [rootPost, moderationOpts])
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ let Feed = ({
|
||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||
const checkForNewRef = React.useRef<(() => void) | null>(null)
|
||||
const lastFetchRef = React.useRef<number>(Date.now())
|
||||
const feedType = feed.split('|')[0]
|
||||
|
||||
const opts = React.useMemo(
|
||||
() => ({enabled, ignoreFilterFor}),
|
||||
@@ -214,6 +215,10 @@ let Feed = ({
|
||||
|
||||
const onRefresh = React.useCallback(async () => {
|
||||
track('Feed:onRefresh')
|
||||
logEvent('feed:refresh', {
|
||||
feedType: feedType,
|
||||
reason: 'pull-to-refresh',
|
||||
})
|
||||
setIsPTRing(true)
|
||||
try {
|
||||
await refetch()
|
||||
@@ -222,9 +227,8 @@ let Feed = ({
|
||||
logger.error('Failed to refresh posts feed', {message: err})
|
||||
}
|
||||
setIsPTRing(false)
|
||||
}, [refetch, track, setIsPTRing, onHasNew])
|
||||
}, [refetch, track, setIsPTRing, onHasNew, feedType])
|
||||
|
||||
const feedType = feed.split('|')[0]
|
||||
const onEndReached = React.useCallback(async () => {
|
||||
if (isFetching || !hasNextPage || isError) return
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export function FeedErrorMessage({
|
||||
if (
|
||||
typeof knownError !== 'undefined' &&
|
||||
knownError !== KnownError.Unknown &&
|
||||
feedDesc.startsWith('feedgen')
|
||||
(feedDesc.startsWith('feedgen') || knownError === KnownError.FeedNSFPublic)
|
||||
) {
|
||||
return (
|
||||
<FeedgenErrorMessage
|
||||
@@ -240,6 +240,9 @@ function detectKnownError(
|
||||
if (typeof error !== 'string') {
|
||||
error = error.toString()
|
||||
}
|
||||
if (error.includes(KnownError.FeedNSFPublic)) {
|
||||
return KnownError.FeedNSFPublic
|
||||
}
|
||||
if (!feedDesc.startsWith('feedgen')) {
|
||||
return KnownError.Unknown
|
||||
}
|
||||
@@ -263,8 +266,5 @@ function detectKnownError(
|
||||
if (error.includes('feed provided an invalid response')) {
|
||||
return KnownError.FeedgenBadResponse
|
||||
}
|
||||
if (error.includes(KnownError.FeedNSFPublic)) {
|
||||
return KnownError.FeedNSFPublic
|
||||
}
|
||||
return KnownError.FeedgenUnknown
|
||||
}
|
||||
|
||||
@@ -22,18 +22,24 @@ export function TestCtrls() {
|
||||
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
|
||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||
const onPressSignInAlice = async () => {
|
||||
await login({
|
||||
service: 'http://localhost:3000',
|
||||
identifier: 'alice.test',
|
||||
password: 'hunter2',
|
||||
})
|
||||
await login(
|
||||
{
|
||||
service: 'http://localhost:3000',
|
||||
identifier: 'alice.test',
|
||||
password: 'hunter2',
|
||||
},
|
||||
'LoginForm',
|
||||
)
|
||||
}
|
||||
const onPressSignInBob = async () => {
|
||||
await login({
|
||||
service: 'http://localhost:3000',
|
||||
identifier: 'bob.test',
|
||||
password: 'hunter2',
|
||||
})
|
||||
await login(
|
||||
{
|
||||
service: 'http://localhost:3000',
|
||||
identifier: 'bob.test',
|
||||
password: 'hunter2',
|
||||
},
|
||||
'LoginForm',
|
||||
)
|
||||
}
|
||||
return (
|
||||
<View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}>
|
||||
@@ -51,7 +57,7 @@ export function TestCtrls() {
|
||||
/>
|
||||
<Pressable
|
||||
testID="e2eSignOut"
|
||||
onPress={() => logout()}
|
||||
onPress={() => logout('Settings')}
|
||||
accessibilityRole="button"
|
||||
style={BTN}
|
||||
/>
|
||||
|
||||
@@ -298,7 +298,10 @@ let EditableUserAvatar = ({
|
||||
<Menu.Root>
|
||||
<Menu.Trigger label={_(msg`Edit avatar`)}>
|
||||
{({props}) => (
|
||||
<TouchableOpacity {...props} activeOpacity={0.8}>
|
||||
<TouchableOpacity
|
||||
{...props}
|
||||
activeOpacity={0.8}
|
||||
testID="changeAvatarBtn">
|
||||
{avatar ? (
|
||||
<HighPriorityImage
|
||||
testID="userAvatarImage"
|
||||
|
||||
@@ -84,7 +84,10 @@ export function UserBanner({
|
||||
<Menu.Root>
|
||||
<Menu.Trigger label={_(msg`Edit avatar`)}>
|
||||
{({props}) => (
|
||||
<TouchableOpacity {...props} activeOpacity={0.8}>
|
||||
<TouchableOpacity
|
||||
{...props}
|
||||
activeOpacity={0.8}
|
||||
testID="changeBannerBtn">
|
||||
{banner ? (
|
||||
<Image
|
||||
testID="userBannerImage"
|
||||
|
||||
@@ -237,7 +237,7 @@ const styles = StyleSheet.create({
|
||||
paddingRight: 12,
|
||||
borderRadius: 8,
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
|
||||
outline: 0,
|
||||
border: 0,
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import {Text as RNText, TextProps} from 'react-native'
|
||||
import {s, lh} from 'lib/styles'
|
||||
import {useTheme, TypographyVariant} from 'lib/ThemeContext'
|
||||
import {isIOS} from 'platform/detection'
|
||||
import {isIOS, isWeb} from 'platform/detection'
|
||||
import {UITextView} from 'react-native-ui-text-view'
|
||||
|
||||
export type CustomTextProps = TextProps & {
|
||||
@@ -13,6 +13,11 @@ export type CustomTextProps = TextProps & {
|
||||
selectable?: boolean
|
||||
}
|
||||
|
||||
const fontFamilyStyle = {
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
|
||||
}
|
||||
|
||||
export function Text({
|
||||
type = 'md',
|
||||
children,
|
||||
@@ -39,7 +44,13 @@ export function Text({
|
||||
|
||||
return (
|
||||
<RNText
|
||||
style={[s.black, typography, lineHeightStyle, style]}
|
||||
style={[
|
||||
s.black,
|
||||
typography,
|
||||
isWeb && fontFamilyStyle,
|
||||
lineHeightStyle,
|
||||
style,
|
||||
]}
|
||||
// @ts-ignore web only -esb
|
||||
dataSet={Object.assign({tooltip: title}, dataSet || {})}
|
||||
selectable={selectable}
|
||||
|
||||
@@ -131,7 +131,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
||||
<Trans>
|
||||
You have not blocked any accounts yet. To block an account, go
|
||||
to their profile and selected "Block account" from the menu on
|
||||
to their profile and select "Block account" from the menu on
|
||||
their account.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -130,8 +130,8 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
||||
<Trans>
|
||||
You have not muted any accounts yet. To mute an account, go to
|
||||
their profile and selected "Mute account" from the menu on
|
||||
their account.
|
||||
their profile and select "Mute account" from the menu on their
|
||||
account.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -100,7 +100,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
|
||||
{isCurrentAccount ? (
|
||||
<TouchableOpacity
|
||||
testID="signOutBtn"
|
||||
onPress={logout}
|
||||
onPress={() => {
|
||||
logout('Settings')
|
||||
}}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Sign out`)}
|
||||
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}>
|
||||
@@ -129,7 +131,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
|
||||
testID={`switchToAccountBtn-${account.handle}`}
|
||||
key={account.did}
|
||||
onPress={
|
||||
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
|
||||
isSwitchingAccounts
|
||||
? undefined
|
||||
: () => onPressSwitchAccount(account, 'Settings')
|
||||
}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Switch to ${account.handle}`)}
|
||||
@@ -711,7 +715,7 @@ export function SettingsScreen({}: Props) {
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Change handle`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for choosing or creating a new Bluesky username`,
|
||||
msg`Opens modal for choosing a new Bluesky handle`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
@@ -772,7 +776,7 @@ export function SettingsScreen({}: Props) {
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Export my data`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens modal for downloading Bluesky account data (repository)`,
|
||||
msg`Opens modal for downloading your Bluesky account data (repository)`,
|
||||
)}>
|
||||
<View style={[styles.iconContainer, pal.btn]}>
|
||||
<FontAwesomeIcon
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@
|
||||
scrollbar-gutter: stable both-edges;
|
||||
}
|
||||
html, body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Buttons and inputs have a font set by UA, so we'll have to reset that */
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
/* ProseMirror */
|
||||
.ProseMirror {
|
||||
font: 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font: 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif;
|
||||
min-height: 140px;
|
||||
}
|
||||
.ProseMirror-dark {
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
jsonpointer "^5.0.0"
|
||||
leven "^3.1.0"
|
||||
|
||||
"@atproto/api@^0.12.1":
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.1.tgz#3340cbbd6a51a8c2f3248dae55a01415ab71084e"
|
||||
integrity sha512-Grigs9neuQxytXr2yHq/IfNlgXQVptWDO9KTQr5FDmgMY4Zly2X7Sa99u9c1CW9auwUTbcd+yRFBNEtbA3n3qg==
|
||||
"@atproto/api@^0.12.2":
|
||||
version "0.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.2.tgz#5df6d4f60dea0395c84fdebd9e81a7e853edf130"
|
||||
integrity sha512-UVzCiDZH2j0wrr/O8nb1edD5cYLVqB5iujueXUCbHS3rAwIxgmyLtA3Hzm2QYsGPo/+xsIg1fNvpq9rNT6KWUA==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.3.0"
|
||||
"@atproto/lexicon" "^0.4.0"
|
||||
|
||||
Reference in New Issue
Block a user