Compare commits

...

4 Commits

Author SHA1 Message Date
Samuel Newman 3ff4ced9eb revert to embedded 2025-10-21 17:30:54 +03:00
Samuel Newman bda67ed24e fix lint 2025-10-21 17:09:01 +03:00
Samuel Newman 55d022ace9 more testbed 2025-10-21 17:02:29 +03:00
Samuel Newman 9bf7ec7b71 crash testbed 2025-10-21 12:40:49 +03:00
4 changed files with 238 additions and 162 deletions
+113 -7
View File
@@ -3,6 +3,7 @@ import '#/logger/bitdrift/setup'
import '#/view/icons'
import React, {useEffect, useState} from 'react'
import {View} from 'react-native'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {
initialWindowMetrics,
@@ -60,22 +61,26 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
import {TestCtrls} from '#/view/com/testing/TestCtrls'
import * as Toast from '#/view/com/util/Toast'
import {Shell} from '#/view/shell'
import {ThemeProvider as Alf} from '#/alf'
import {atoms as a, ThemeProvider as Alf} from '#/alf'
import {useColorModeTheme} from '#/alf/util/useColorModeTheme'
import {Button, ButtonText} from '#/components/Button'
import {Provider as ContextMenuProvider} from '#/components/ContextMenu'
import {NuxDialogs} from '#/components/dialogs/nuxs'
import * as Dialog from '#/components/Dialog'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PolicyUpdateOverlayProvider} from '#/components/PolicyUpdateOverlay'
import {Outlet as PortalOutlet} from '#/components/Portal'
import {Provider as PortalProvider} from '#/components/Portal'
import {Provider as VideoVolumeProvider} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
import {ToastOutlet} from '#/components/Toast'
import {Splash} from '#/Splash'
import {BottomSheetOutlet} from '../modules/bottom-sheet'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import {useApplyPullRequestOTAUpdate} from './lib/hooks/useOTAUpdates'
import {RequestListItem} from './screens/Messages/components/RequestListItem'
import {List} from './view/com/util/List'
SplashScreen.preventAutoHideAsync()
if (isIOS) {
@@ -164,10 +169,10 @@ function InnerApp() {
style={s.h100pct}>
<GlobalGestureEventsProvider>
<IntentDialogProvider>
<TestCtrls />
<Shell />
<NuxDialogs />
<Testbed />
<ToastOutlet />
<PortalOutlet />
<BottomSheetOutlet />
</IntentDialogProvider>
</GlobalGestureEventsProvider>
</GestureHandlerRootView>
@@ -252,3 +257,104 @@ function App() {
}
export default Sentry.wrap(App)
function Testbed() {
const control = Dialog.useDialogControl()
const [showItem, setShowItem] = useState(true)
const {revertToEmbedded} = useApplyPullRequestOTAUpdate()
return (
<View style={[a.align_center, a.justify_center, {gap: 16}, a.h_full]}>
<Button
label="show dialog"
onPress={() => {
control.open()
}}
size="large"
color="primary">
<ButtonText>Show dialog</ButtonText>
</Button>
<Button
label="toggle list item + show toast"
onPress={() => {
Toast.show('toggle list item')
setShowItem(x => !x)
}}
size="large"
color="primary">
<ButtonText>toggle list item + show toast</ButtonText>
</Button>
<List
style={[{width: '100%', height: 300}, a.flex_grow_0]}
data={showItem ? [true] : []}
renderItem={() => (
<RequestListItem
convo={{
id: 'abc',
members: [{did: 'did:plc:abc', handle: 'alice.example.com'}],
muted: false,
rev: '1',
unreadCount: 0,
}}
/>
)}
/>
<Button
label="revert to embedded js"
onPress={() => revertToEmbedded()}
size="large"
color="secondary">
<ButtonText>revert to embedded js</ButtonText>
</Button>
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label=""
style={{height: 300}}
contentContainerStyle={{gap: 12}}>
<Button
label="show dialog"
onPress={() => Toast.show('Hello')}
size="large"
color="secondary">
<ButtonText>Show dialog</ButtonText>
</Button>
<Button
label="close dialog"
onPress={() => {
Toast.show('closing')
control.close()
}}
size="large"
color="primary">
<ButtonText>Close - toast before</ButtonText>
</Button>
<Button
label="close dialog"
onPress={() => {
control.close(() => {
Toast.show('closed')
})
}}
size="large"
color="primary">
<ButtonText>Close - toast after</ButtonText>
</Button>
<Button
label="close dialog"
onPress={() => {
control.close(() => {
setShowItem(false)
Toast.show('closed')
})
}}
size="large"
color="primary">
<ButtonText>Close - close + hide item</ButtonText>
</Button>
</Dialog.ScrollableInner>
</Dialog.Outer>
</View>
)
}
+116 -135
View File
@@ -30,16 +30,14 @@ import {
import {precacheProfile} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme, web} from '#/alf'
import * as tokens from '#/alf/tokens'
import {Button} from '#/components/Button'
import {useDialogControl} from '#/components/Dialog'
import {ConvoMenu} from '#/components/dms/ConvoMenu'
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2'
import {Envelope_Open_Stroke2_Corner0_Rounded as EnvelopeOpen} from '#/components/icons/EnveopeOpen'
import {Trash_Stroke2_Corner0_Rounded} from '#/components/icons/Trash'
import {Link} from '#/components/Link'
import {useMenuControl} from '#/components/Menu'
import {PostAlerts} from '#/components/moderation/PostAlerts'
import {createPortalGroup} from '#/components/Portal'
@@ -87,7 +85,6 @@ function ChatListItemReady({
convo,
profile: profileUnshadowed,
moderationOpts,
showMenu,
children,
}: {
convo: ChatBskyConvoDefs.ConvoView
@@ -101,7 +98,6 @@ function ChatListItemReady({
const {currentAccount} = useSession()
const menuControl = useMenuControl()
const leaveConvoControl = useDialogControl()
const {gtMobile} = useBreakpoints()
const profile = useProfileShadow(profileUnshadowed)
const {mutate: markAsRead} = useMarkAsReadMutation()
const moderation = React.useMemo(
@@ -115,17 +111,6 @@ function ChatListItemReady({
profile,
})
const blockInfo = useMemo(() => {
const modui = moderation.ui('profileView')
const blocks = modui.alerts.filter(alert => alert.type === 'blocking')
const listBlocks = blocks.filter(alert => alert.source.type === 'list')
const userBlock = blocks.find(alert => alert.source.type === 'user')
return {
listBlocks,
userBlock,
}
}, [moderation])
const isDeletedAccount = profile.handle === 'missing.invalid'
const displayName = isDeletedAccount
? _(msg`Deleted Account`)
@@ -136,137 +121,134 @@ function ChatListItemReady({
const isDimStyle = convo.muted || moderation.blocked || isDeletedAccount
const {lastMessage, lastMessageSentAt, latestReportableMessage} =
useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-shadow
let lastMessage = _(msg`No messages yet`)
// eslint-disable-next-line @typescript-eslint/no-shadow
let lastMessageSentAt: string | null = null
// eslint-disable-next-line @typescript-eslint/no-shadow
let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined
const {lastMessage, lastMessageSentAt} = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-shadow
let lastMessage = _(msg`No messages yet`)
// eslint-disable-next-line @typescript-eslint/no-shadow
let lastMessageSentAt: string | null = null
if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
const isFromMe = convo.lastMessage.sender?.did === currentAccount?.did
let latestReportableMessage: ChatBskyConvoDefs.MessageView | undefined
if (!isFromMe) {
latestReportableMessage = convo.lastMessage
if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) {
const isFromMe = convo.lastMessage.sender?.did === currentAccount?.did
if (!isFromMe) {
latestReportableMessage = convo.lastMessage
}
if (convo.lastMessage.text) {
if (isFromMe) {
lastMessage = _(msg`You: ${convo.lastMessage.text}`)
} else {
lastMessage = convo.lastMessage.text
}
} else if (convo.lastMessage.embed) {
const defaultEmbeddedContentMessage = _(
msg`(contains embedded content)`,
)
if (convo.lastMessage.text) {
if (isFromMe) {
lastMessage = _(msg`You: ${convo.lastMessage.text}`)
} else {
lastMessage = convo.lastMessage.text
}
} else if (convo.lastMessage.embed) {
const defaultEmbeddedContentMessage = _(
msg`(contains embedded content)`,
)
if (AppBskyEmbedRecord.isView(convo.lastMessage.embed)) {
const embed = convo.lastMessage.embed
if (AppBskyEmbedRecord.isView(convo.lastMessage.embed)) {
const embed = convo.lastMessage.embed
if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
const record = embed.record
const path = postUriToRelativePath(record.uri, {
handle: record.author.handle,
})
const href = path ? toBskyAppUrl(path) : undefined
const short = href
? toShortUrl(href)
: defaultEmbeddedContentMessage
if (isFromMe) {
lastMessage = _(msg`You: ${short}`)
} else {
lastMessage = short
}
}
} else {
if (AppBskyEmbedRecord.isViewRecord(embed.record)) {
const record = embed.record
const path = postUriToRelativePath(record.uri, {
handle: record.author.handle,
})
const href = path ? toBskyAppUrl(path) : undefined
const short = href
? toShortUrl(href)
: defaultEmbeddedContentMessage
if (isFromMe) {
lastMessage = _(msg`You: ${defaultEmbeddedContentMessage}`)
lastMessage = _(msg`You: ${short}`)
} else {
lastMessage = defaultEmbeddedContentMessage
lastMessage = short
}
}
}
lastMessageSentAt = convo.lastMessage.sentAt
}
if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) {
lastMessageSentAt = convo.lastMessage.sentAt
lastMessage = isDeletedAccount
? _(msg`Conversation deleted`)
: _(msg`Message deleted`)
}
if (ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) {
if (
!lastMessageSentAt ||
new Date(lastMessageSentAt) <
new Date(convo.lastReaction.reaction.createdAt)
) {
const isFromMe =
convo.lastReaction.reaction.sender.did === currentAccount?.did
const lastMessageText = convo.lastReaction.message.text
const fallbackMessage = _(
msg({
message: 'a message',
comment: `If last message does not contain text, fall back to "{user} reacted to {a message}"`,
}),
)
} else {
if (isFromMe) {
lastMessage = _(msg`You: ${defaultEmbeddedContentMessage}`)
} else {
lastMessage = defaultEmbeddedContentMessage
}
}
}
lastMessageSentAt = convo.lastMessage.sentAt
}
if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) {
lastMessageSentAt = convo.lastMessage.sentAt
lastMessage = isDeletedAccount
? _(msg`Conversation deleted`)
: _(msg`Message deleted`)
}
if (ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) {
if (
!lastMessageSentAt ||
new Date(lastMessageSentAt) <
new Date(convo.lastReaction.reaction.createdAt)
) {
const isFromMe =
convo.lastReaction.reaction.sender.did === currentAccount?.did
const lastMessageText = convo.lastReaction.message.text
const fallbackMessage = _(
msg({
message: 'a message',
comment: `If last message does not contain text, fall back to "{user} reacted to {a message}"`,
}),
)
if (isFromMe) {
lastMessage = _(
msg`You reacted ${convo.lastReaction.reaction.value} to ${
lastMessageText
? `"${convo.lastReaction.message.text}"`
: fallbackMessage
}`,
)
} else {
const senderDid = convo.lastReaction.reaction.sender.did
const sender = convo.members.find(member => member.did === senderDid)
if (sender) {
lastMessage = _(
msg`You reacted ${convo.lastReaction.reaction.value} to ${
msg`${sanitizeDisplayName(
sender.displayName || sender.handle,
)} reacted ${convo.lastReaction.reaction.value} to ${
lastMessageText
? `"${convo.lastReaction.message.text}"`
: fallbackMessage
}`,
)
} else {
const senderDid = convo.lastReaction.reaction.sender.did
const sender = convo.members.find(
member => member.did === senderDid,
lastMessage = _(
msg`Someone reacted ${convo.lastReaction.reaction.value} to ${
lastMessageText
? `"${convo.lastReaction.message.text}"`
: fallbackMessage
}`,
)
if (sender) {
lastMessage = _(
msg`${sanitizeDisplayName(
sender.displayName || sender.handle,
)} reacted ${convo.lastReaction.reaction.value} to ${
lastMessageText
? `"${convo.lastReaction.message.text}"`
: fallbackMessage
}`,
)
} else {
lastMessage = _(
msg`Someone reacted ${convo.lastReaction.reaction.value} to ${
lastMessageText
? `"${convo.lastReaction.message.text}"`
: fallbackMessage
}`,
)
}
}
}
}
}
return {
lastMessage,
lastMessageSentAt,
latestReportableMessage,
}
}, [
_,
convo.lastMessage,
convo.lastReaction,
currentAccount?.did,
isDeletedAccount,
convo.members,
])
return {
lastMessage,
lastMessageSentAt,
latestReportableMessage,
}
}, [
_,
convo.lastMessage,
convo.lastReaction,
currentAccount?.did,
isDeletedAccount,
convo.members,
])
const [showActions, setShowActions] = useState(false)
const [_showActions, setShowActions] = useState(false)
const onMouseEnter = useCallback(() => {
setShowActions(true)
@@ -349,15 +331,14 @@ function ChatListItemReady({
a.absolute,
{top: tokens.space.md, left: tokens.space.lg},
]}>
<PreviewableUserAvatar
profile={profile}
<UserAvatar
type="user"
size={52}
moderation={moderation.ui('avatar')}
/>
</View>
<Link
to={`/messages/${convo.id}`}
<Button
label={displayName}
accessibilityHint={
!isDeletedAccount
@@ -509,10 +490,10 @@ function ChatListItemReady({
)}
</View>
)}
</Link>
</Button>
<ChatListItemPortal.Outlet />
{/*
{showMenu && (
<ConvoMenu
convo={convo}
@@ -535,12 +516,12 @@ function ChatListItemReady({
]}
latestReportableMessage={latestReportableMessage}
/>
)}
<LeaveConvoPrompt
)}*/}
{/*<LeaveConvoPrompt
control={leaveConvoControl}
convoId={convo.id}
currentScreen="list"
/>
/>*/}
</View>
</GestureActionView>
</ChatListItemPortal.Provider>
@@ -2,10 +2,8 @@ import {useCallback} from 'react'
import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {StackActions, useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {type NavigationProp} from '#/lib/routes/types'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useEmail} from '#/state/email-verification'
import {useAcceptConversation} from '#/state/queries/messages/accept-conversation'
@@ -50,11 +48,11 @@ export function RejectMenu({
}) {
const {_} = useLingui()
const shadowedProfile = useProfileShadow(profile)
const navigation = useNavigation<NavigationProp>()
// const navigation = useNavigation<NavigationProp>()
const {mutate: leaveConvo} = useLeaveConvo(convo.id, {
onMutate: () => {
if (currentScreen === 'conversation') {
navigation.dispatch(StackActions.pop())
// navigation.dispatch(StackActions.pop())
}
},
onError: () => {
@@ -192,7 +190,7 @@ export function AcceptChatButton({
}) {
const {_} = useLingui()
const queryClient = useQueryClient()
const navigation = useNavigation<NavigationProp>()
// const navigation = useNavigation<NavigationProp>()
const {needsEmailVerification} = useEmail()
const emailDialogControl = useEmailDialogControl()
@@ -201,10 +199,10 @@ export function AcceptChatButton({
onAcceptConvo?.()
if (currentScreen === 'list') {
precacheConvoQuery(queryClient, {...convo, status: 'accepted'})
navigation.navigate('MessagesConversation', {
conversation: convo.id,
accept: true,
})
// navigation.navigate('MessagesConversation', {
// conversation: convo.id,
// accept: true,
// })
}
},
onError: () => {
@@ -272,12 +270,12 @@ export function DeleteChatButton({
currentScreen: 'list' | 'conversation'
}) {
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
// const navigation = useNavigation<NavigationProp>()
const {mutate: leaveConvo} = useLeaveConvo(convo.id, {
onMutate: () => {
if (currentScreen === 'conversation') {
navigation.dispatch(StackActions.pop())
// navigation.dispatch(StackActions.pop())
}
},
onError: () => {
@@ -5,7 +5,6 @@ import {Trans} from '@lingui/macro'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useSession} from '#/state/session'
import {atoms as a, tokens} from '#/alf'
import {KnownFollowers} from '#/components/KnownFollowers'
import {Text} from '#/components/Typography'
import {ChatListItem, ChatListItemPortal} from './ChatListItem'
import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons'
@@ -27,14 +26,6 @@ export function RequestListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) {
return (
<View style={[a.relative, a.flex_1]}>
<ChatListItem convo={convo} showMenu={false}>
<View style={[a.pt_xs, a.pb_2xs]}>
<KnownFollowers
profile={otherUser}
moderationOpts={moderationOpts}
minimal
showIfEmpty
/>
</View>
{/* spacer, since you can't nest pressables */}
<View style={[a.pt_md, a.pb_xs, a.w_full, {opacity: 0}]} aria-hidden>
{/* Placeholder text so that it responds to the font height */}