Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8aaea79d4c | |||
| cf73b8fe4d | |||
| bd061f983e | |||
| 019aae5f01 | |||
| 7c51a3931a | |||
| d1c14940fb | |||
| 0dfc039a47 | |||
| c858b58307 | |||
| 9c8a1b8a31 |
Vendored
+6
-4
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useState, useCallback, useRef} from 'react'
|
||||
import {useEffect, useState, useMemo, useCallback, useRef} from 'react'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {Shadow} from './types'
|
||||
@@ -55,9 +55,11 @@ export function usePostShadow(
|
||||
firstRun.current = false
|
||||
}, [post])
|
||||
|
||||
return state.ts > ifAfterTS
|
||||
? mergeShadow(post, state.value)
|
||||
: {...post, isShadowed: true}
|
||||
return useMemo(() => {
|
||||
return state.ts > ifAfterTS
|
||||
? mergeShadow(post, state.value)
|
||||
: {...post, isShadowed: true}
|
||||
}, [post, state, ifAfterTS])
|
||||
}
|
||||
|
||||
export function updatePostShadow(uri: string, value: Partial<PostShadow>) {
|
||||
|
||||
Vendored
+6
-4
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useState, useCallback, useRef} from 'react'
|
||||
import {useEffect, useState, useMemo, useCallback, useRef} from 'react'
|
||||
import EventEmitter from 'eventemitter3'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {Shadow} from './types'
|
||||
@@ -56,9 +56,11 @@ export function useProfileShadow(
|
||||
firstRun.current = false
|
||||
}, [profile])
|
||||
|
||||
return state.ts > ifAfterTS
|
||||
? mergeShadow(profile, state.value)
|
||||
: {...profile, isShadowed: true}
|
||||
return useMemo(() => {
|
||||
return state.ts > ifAfterTS
|
||||
? mergeShadow(profile, state.value)
|
||||
: {...profile, isShadowed: true}
|
||||
}, [profile, state, ifAfterTS])
|
||||
}
|
||||
|
||||
export function updateProfileShadow(
|
||||
|
||||
@@ -128,6 +128,8 @@ export async function migrate() {
|
||||
const newData = transform(legacyData)
|
||||
await write(newData)
|
||||
logger.debug('persisted state: migrated legacy storage')
|
||||
} else {
|
||||
logger.debug('persisted state: no migration needed')
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('persisted state: error migrating legacy storage', {
|
||||
@@ -135,3 +137,13 @@ export async function migrate() {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearLegacyStorage() {
|
||||
try {
|
||||
await AsyncStorage.removeItem(DEPRECATED_ROOT_STATE_STORAGE_KEY)
|
||||
} catch (e: any) {
|
||||
logger.error(`persisted legacy store: failed to clear`, {
|
||||
error: e.toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
import {Schema, schema} from '#/state/persisted/schema'
|
||||
import {logger} from '#/logger'
|
||||
|
||||
const BSKY_STORAGE = 'BSKY_STORAGE'
|
||||
|
||||
@@ -16,3 +17,11 @@ export async function read(): Promise<Schema | undefined> {
|
||||
return objData
|
||||
}
|
||||
}
|
||||
|
||||
export async function clear() {
|
||||
try {
|
||||
await AsyncStorage.removeItem(BSKY_STORAGE)
|
||||
} catch (e: any) {
|
||||
logger.error(`persisted store: failed to clear`, {error: e.toString()})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useMemo} from 'react'
|
||||
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
import {
|
||||
LabelPreference,
|
||||
BskyFeedViewPreference,
|
||||
ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import {LabelPreference, BskyFeedViewPreference} from '@atproto/api'
|
||||
|
||||
import {track} from '#/lib/analytics/analytics'
|
||||
import {getAge} from '#/lib/strings/time'
|
||||
@@ -91,21 +86,16 @@ export function usePreferencesQuery() {
|
||||
|
||||
export function useModerationOpts() {
|
||||
const {currentAccount} = useSession()
|
||||
const [opts, setOpts] = useState<ModerationOpts | undefined>()
|
||||
const prefs = usePreferencesQuery()
|
||||
useEffect(() => {
|
||||
const opts = useMemo(() => {
|
||||
if (!prefs.data) {
|
||||
return
|
||||
}
|
||||
// only update this hook when the moderation options change
|
||||
const newOpts = getModerationOpts({
|
||||
return getModerationOpts({
|
||||
userDid: currentAccount?.did || '',
|
||||
preferences: prefs.data,
|
||||
})
|
||||
if (!isEqual(opts, newOpts)) {
|
||||
setOpts(newOpts)
|
||||
}
|
||||
}, [prefs.data, currentAccount, opts, setOpts])
|
||||
}, [currentAccount?.did, prefs.data])
|
||||
return opts
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,12 @@ export function ProfileLists({
|
||||
items = items.concat([EMPTY])
|
||||
} else if (data?.pages) {
|
||||
for (const page of data?.pages) {
|
||||
items = items.concat(page.lists)
|
||||
items = items.concat(
|
||||
page.lists.map(l => ({
|
||||
...l,
|
||||
_reactKey: l.uri,
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isError && !isEmpty) {
|
||||
|
||||
@@ -212,7 +212,9 @@ export function Component() {
|
||||
<Button
|
||||
testID="cancelBtn"
|
||||
type="default"
|
||||
onPress={() => closeModal()}
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}
|
||||
accessibilityLabel={_(msg`Cancel`)}
|
||||
accessibilityHint=""
|
||||
label={_(msg`Cancel`)}
|
||||
|
||||
@@ -79,7 +79,9 @@ export function Component({text, href}: {text: string; href: string}) {
|
||||
<Button
|
||||
testID="cancelBtn"
|
||||
type="default"
|
||||
onPress={() => closeModal()}
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}
|
||||
accessibilityLabel={_(msg`Cancel`)}
|
||||
accessibilityHint=""
|
||||
label="Cancel"
|
||||
|
||||
@@ -136,7 +136,9 @@ export function Component({
|
||||
<Button
|
||||
testID="doneBtn"
|
||||
type="default"
|
||||
onPress={() => closeModal()}
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}
|
||||
accessibilityLabel={_(msg`Done`)}
|
||||
accessibilityHint=""
|
||||
label="Done"
|
||||
|
||||
@@ -99,7 +99,12 @@ export function Component({
|
||||
{description}
|
||||
</Text>
|
||||
<View style={s.flex1} />
|
||||
<Button type="primary" style={styles.btn} onPress={() => closeModal()}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={styles.btn}
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}>
|
||||
<Text type="button-lg" style={[pal.textLight, s.textCenter, s.white]}>
|
||||
Okay
|
||||
</Text>
|
||||
|
||||
@@ -224,7 +224,9 @@ export function Component({showReminder}: {showReminder?: boolean}) {
|
||||
<Button
|
||||
testID="cancelBtn"
|
||||
type="default"
|
||||
onPress={() => closeModal()}
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}
|
||||
accessibilityLabel={
|
||||
stage === Stages.Reminder ? 'Not right now' : 'Cancel'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useMemo, useState, useEffect} from 'react'
|
||||
import React, {memo, useMemo, useState, useEffect} from 'react'
|
||||
import {
|
||||
Animated,
|
||||
TouchableOpacity,
|
||||
@@ -56,7 +56,7 @@ interface Author {
|
||||
moderation: ProfileModeration
|
||||
}
|
||||
|
||||
export function FeedItem({
|
||||
let FeedItem = ({
|
||||
item,
|
||||
dataUpdatedAt,
|
||||
moderationOpts,
|
||||
@@ -64,7 +64,7 @@ export function FeedItem({
|
||||
item: FeedNotification
|
||||
dataUpdatedAt: number
|
||||
moderationOpts: ModerationOpts
|
||||
}) {
|
||||
}): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
|
||||
const itemHref = useMemo(() => {
|
||||
@@ -262,6 +262,8 @@ export function FeedItem({
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
FeedItem = memo(FeedItem)
|
||||
export {FeedItem}
|
||||
|
||||
function ExpandListPressable({
|
||||
hasMultipleAuthors,
|
||||
|
||||
@@ -22,7 +22,7 @@ import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
|
||||
|
||||
const SCROLLED_DOWN_LIMIT = 200
|
||||
|
||||
interface PagerWithHeaderChildParams {
|
||||
export interface PagerWithHeaderChildParams {
|
||||
headerHeight: number
|
||||
isFocused: boolean
|
||||
onScroll: OnScrollHandler
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useMemo} from 'react'
|
||||
import React, {memo, useMemo} from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {
|
||||
AtUri,
|
||||
@@ -118,7 +118,7 @@ function PostThreadItemDeleted() {
|
||||
)
|
||||
}
|
||||
|
||||
function PostThreadItemLoaded({
|
||||
let PostThreadItemLoaded = ({
|
||||
post,
|
||||
record,
|
||||
richText,
|
||||
@@ -144,12 +144,12 @@ function PostThreadItemLoaded({
|
||||
showParentReplyLine?: boolean
|
||||
hasPrecedingItem: boolean
|
||||
onPostReply: () => void
|
||||
}) {
|
||||
}): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
const langPrefs = useLanguagePrefs()
|
||||
const {openComposer} = useComposerControls()
|
||||
const [limitLines, setLimitLines] = React.useState(
|
||||
countLines(richText?.text) >= MAX_POST_LINES,
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
const styles = useStyles()
|
||||
const hasEngagement = post.likeCount || post.repostCount
|
||||
@@ -565,6 +565,7 @@ function PostThreadItemLoaded({
|
||||
)
|
||||
}
|
||||
}
|
||||
PostThreadItemLoaded = memo(PostThreadItemLoaded)
|
||||
|
||||
function PostOuterWrapper({
|
||||
post,
|
||||
|
||||
@@ -99,7 +99,7 @@ function PostInner({
|
||||
const pal = usePalette('default')
|
||||
const {openComposer} = useComposerControls()
|
||||
const [limitLines, setLimitLines] = useState(
|
||||
countLines(richText?.text) >= MAX_POST_LINES,
|
||||
() => countLines(richText?.text) >= MAX_POST_LINES,
|
||||
)
|
||||
const itemUrip = new AtUri(post.uri)
|
||||
const itemHref = makeProfileLink(post.author, 'post', itemUrip.rkey)
|
||||
|
||||
@@ -106,7 +106,7 @@ let FeedItemInner = ({
|
||||
const pal = usePalette('default')
|
||||
const {track} = useAnalytics()
|
||||
const [limitLines, setLimitLines] = useState(
|
||||
countLines(richText.text) >= MAX_POST_LINES,
|
||||
() => countLines(richText.text) >= MAX_POST_LINES,
|
||||
)
|
||||
|
||||
const href = useMemo(() => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
|
||||
import {ViewSelectorHandle} from '../com/util/ViewSelector'
|
||||
import {CenteredView} from '../com/util/Views'
|
||||
import {CenteredView, FlatList} from '../com/util/Views'
|
||||
import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
|
||||
import {Feed} from 'view/com/posts/Feed'
|
||||
import {ProfileLists} from '../com/lists/ProfileLists'
|
||||
@@ -38,6 +38,10 @@ import {useQueryClient} from '@tanstack/react-query'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {listenSoftReset} from '#/state/events'
|
||||
|
||||
interface SectionRef {
|
||||
scrollToTop: () => void
|
||||
}
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||
export const ProfileScreen = withAuthRequired(function ProfileScreenImpl({
|
||||
route,
|
||||
@@ -193,7 +197,7 @@ function ProfileScreenLoaded({
|
||||
}, [openComposer, currentAccount, track, profile])
|
||||
|
||||
const onPageSelected = React.useCallback(
|
||||
i => {
|
||||
(i: number) => {
|
||||
setCurrentPage(i)
|
||||
},
|
||||
[setCurrentPage],
|
||||
@@ -231,7 +235,9 @@ function ProfileScreenLoaded({
|
||||
headerHeight={headerHeight}
|
||||
isFocused={isFocused}
|
||||
isScrolledDown={isScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
||||
@@ -242,7 +248,9 @@ function ProfileScreenLoaded({
|
||||
headerHeight={headerHeight}
|
||||
isFocused={isFocused}
|
||||
isScrolledDown={isScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => (
|
||||
@@ -253,7 +261,9 @@ function ProfileScreenLoaded({
|
||||
headerHeight={headerHeight}
|
||||
isFocused={isFocused}
|
||||
isScrolledDown={isScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showLikesTab
|
||||
@@ -271,7 +281,9 @@ function ProfileScreenLoaded({
|
||||
headerHeight={headerHeight}
|
||||
isFocused={isFocused}
|
||||
isScrolledDown={isScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
@@ -279,7 +291,9 @@ function ProfileScreenLoaded({
|
||||
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
|
||||
<ProfileFeedgens
|
||||
did={profile.did}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1}
|
||||
headerOffset={headerHeight}
|
||||
@@ -291,7 +305,9 @@ function ProfileScreenLoaded({
|
||||
? ({onScroll, headerHeight, isFocused, scrollElRef}) => (
|
||||
<ProfileLists
|
||||
did={profile.did}
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={
|
||||
scrollElRef as React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={1}
|
||||
headerOffset={headerHeight}
|
||||
@@ -318,7 +334,7 @@ interface FeedSectionProps {
|
||||
headerHeight: number
|
||||
isFocused: boolean
|
||||
isScrolledDown: boolean
|
||||
scrollElRef: any /* TODO */
|
||||
scrollElRef: React.MutableRefObject<FlatList<any> | null>
|
||||
}
|
||||
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
|
||||
function FeedSectionImpl(
|
||||
|
||||
@@ -119,29 +119,28 @@ function SearchScreenSuggestedFollows() {
|
||||
|
||||
React.useEffect(() => {
|
||||
async function getSuggestions() {
|
||||
// TODO not quite right, doesn't fetch your follows
|
||||
const friends = await getSuggestedFollowsByActor(
|
||||
currentAccount!.did,
|
||||
).then(friendsRes => friendsRes.suggestions)
|
||||
|
||||
if (!friends) return // :(
|
||||
|
||||
const friendsOfFriends = (
|
||||
await Promise.all(
|
||||
friends
|
||||
.slice(0, 4)
|
||||
.map(friend =>
|
||||
getSuggestedFollowsByActor(friend.did).then(
|
||||
foafsRes => foafsRes.suggestions,
|
||||
),
|
||||
),
|
||||
)
|
||||
).flat()
|
||||
const friendsOfFriends = new Map<
|
||||
string,
|
||||
AppBskyActorDefs.ProfileViewBasic
|
||||
>()
|
||||
|
||||
setSuggestions(
|
||||
// dedupe
|
||||
friendsOfFriends.filter(f => !friends.find(f2 => f.did === f2.did)),
|
||||
await Promise.all(
|
||||
friends.slice(0, 4).map(friend =>
|
||||
getSuggestedFollowsByActor(friend.did).then(foafsRes => {
|
||||
for (const user of foafsRes.suggestions) {
|
||||
friendsOfFriends.set(user.did, user)
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
setSuggestions(Array.from(friendsOfFriends.values()))
|
||||
setDataUpdatedAt(Date.now())
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ import {
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useClearPreferencesMutation} from '#/state/queries/preferences'
|
||||
import {useInviteCodesQuery} from '#/state/queries/invites'
|
||||
import {clear as clearStorage} from '#/state/persisted/store'
|
||||
import {clearLegacyStorage} from '#/state/persisted/legacy'
|
||||
|
||||
// TEMPORARY (APP-700)
|
||||
// remove after backend testing finishes
|
||||
@@ -266,6 +268,15 @@ export const SettingsScreen = withAuthRequired(function Settings({}: Props) {
|
||||
Linking.openURL(STATUS_PAGE_URL)
|
||||
}, [])
|
||||
|
||||
const clearAllStorage = React.useCallback(async () => {
|
||||
await clearStorage()
|
||||
Toast.show(`Storage cleared, you need to restart the app now.`)
|
||||
}, [])
|
||||
const clearAllLegacyStorage = React.useCallback(async () => {
|
||||
await clearLegacyStorage()
|
||||
Toast.show(`Legacy storage cleared, you need to restart the app now.`)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[s.hContentRegion]} testID="settingsScreen">
|
||||
<ViewHeader title="Settings" />
|
||||
@@ -671,6 +682,28 @@ export const SettingsScreen = withAuthRequired(function Settings({}: Props) {
|
||||
<Trans>Reset onboarding state</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[pal.view, styles.linkCardNoIcon]}
|
||||
onPress={clearAllLegacyStorage}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint="Clear all legacy storage data"
|
||||
accessibilityLabel={_(msg`Clear all legacy storage data`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>
|
||||
Clear all legacy storage data (restart after this)
|
||||
</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[pal.view, styles.linkCardNoIcon]}
|
||||
onPress={clearAllStorage}
|
||||
accessibilityRole="button"
|
||||
accessibilityHint="Clear all storage data"
|
||||
accessibilityLabel={_(msg`Clear all storage data`)}>
|
||||
<Text type="lg" style={pal.text}>
|
||||
<Trans>Clear all storage data (restart after this)</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
) : null}
|
||||
<View style={[styles.footer]}>
|
||||
|
||||
Reference in New Issue
Block a user