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 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()
|
||||||
|
|||||||
+10
-8
@@ -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 {
|
||||||
|
|||||||
+33
-3
@@ -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',
|
||||||
|
|||||||
+1
-1
@@ -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",
|
||||||
|
|||||||
+5
-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'},
|
||||||
|
|||||||
+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"
|
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)
|
||||||
|
|||||||
+38
-23
@@ -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
|
||||||
|
|||||||
+2
-2
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user