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