Compare commits

...

9 Commits

Author SHA1 Message Date
Eric Bailey 8aaea79d4c Use try/catch 2023-11-21 10:08:15 -06:00
Eric Bailey cf73b8fe4d Add legacy storage clearing too 2023-11-20 16:06:14 -06:00
Eric Bailey bd061f983e Add dev-only button to clear storage 2023-11-20 15:54:10 -06:00
Eric Bailey 019aae5f01 Improve dedupe logic on search suggestions (#1958) 2023-11-17 12:15:14 -08:00
dan 7c51a3931a Optimize notifications rendering (#1957) 2023-11-17 17:51:44 +00:00
dan d1c14940fb Add missing type (#1955) 2023-11-17 09:47:36 -08:00
Eric Bailey 0dfc039a47 Fix some key errors (#1951) 2023-11-17 11:34:27 -06:00
dan c858b58307 Do less work (#1953) 2023-11-17 09:31:26 -08:00
Eric Bailey 9c8a1b8a31 Fix some type errors (#1952)
* Fix some low-hanging type errors

* Override scrollRef types on profile
2023-11-17 11:08:45 -06:00
19 changed files with 147 additions and 63 deletions
+6 -4
View File
@@ -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>) {
+6 -4
View File
@@ -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(
+12
View File
@@ -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(),
})
}
}
+9
View File
@@ -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()})
}
}
+5 -15
View File
@@ -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
}
+6 -1
View File
@@ -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) {
+3 -1
View File
@@ -212,7 +212,9 @@ export function Component() {
<Button
testID="cancelBtn"
type="default"
onPress={() => closeModal()}
onPress={() => {
closeModal()
}}
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
label={_(msg`Cancel`)}
+3 -1
View File
@@ -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"
+3 -1
View File
@@ -136,7 +136,9 @@ export function Component({
<Button
testID="doneBtn"
type="default"
onPress={() => closeModal()}
onPress={() => {
closeModal()
}}
accessibilityLabel={_(msg`Done`)}
accessibilityHint=""
label="Done"
+6 -1
View File
@@ -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>
+3 -1
View File
@@ -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'
}
+5 -3
View File
@@ -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,
+1 -1
View File
@@ -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
+5 -4
View File
@@ -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,
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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(() => {
+25 -9
View File
@@ -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(
+14 -15
View File
@@ -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())
}
+33
View File
@@ -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]}>