Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c6bd6f16 | |||
| fa34d92da0 |
@@ -0,0 +1,75 @@
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
|
||||
import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function MessagesListPendingAcceptFooter({
|
||||
// recipient: initialRecipient,
|
||||
convoId,
|
||||
hasMessages,
|
||||
}: {
|
||||
recipient: AppBskyActorDefs.ProfileViewBasic
|
||||
convoId: string
|
||||
hasMessages: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
// const recipient = useProfileShadow(initialRecipient)
|
||||
|
||||
const leaveConvoControl = useDialogControl()
|
||||
const reportControl = useDialogControl()
|
||||
|
||||
return (
|
||||
<View style={[hasMessages && a.pt_md, a.pb_xl, a.gap_lg]}>
|
||||
<Divider />
|
||||
<Text style={[a.text_md, a.font_bold, a.text_center]}>
|
||||
{/* {isBlocking ? (
|
||||
<Trans>You have blocked this user</Trans>
|
||||
) : (
|
||||
<Trans>This user has blocked you</Trans>
|
||||
)} */}
|
||||
</Text>
|
||||
|
||||
<View style={[a.flex_row, a.justify_between, a.gap_lg, a.px_md]}>
|
||||
<Button
|
||||
label={_(msg`Leave chat`)}
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
style={[a.flex_1]}
|
||||
onPress={leaveConvoControl.open}>
|
||||
<ButtonText style={{color: t.palette.negative_500}}>
|
||||
<Trans>Leave chat</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label={_(msg`Report`)}
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
style={[a.flex_1]}
|
||||
onPress={reportControl.open}>
|
||||
<ButtonText style={{color: t.palette.negative_500}}>
|
||||
<Trans>Report</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<LeaveConvoPrompt
|
||||
control={leaveConvoControl}
|
||||
currentScreen="conversation"
|
||||
convoId={convoId}
|
||||
/>
|
||||
|
||||
<ReportConversationPrompt control={reportControl} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -23,9 +23,11 @@ import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
|
||||
import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
|
||||
import {MessagesListPendingAcceptFooter} from '#/components/dms/MessagesListPendingAcceptFooter'
|
||||
import {Error} from '#/components/Error'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {ChatDisabled} from './components/ChatDisabled'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -193,14 +195,23 @@ function InnerReady({
|
||||
<MessagesList
|
||||
hasScrolled={hasScrolled}
|
||||
setHasScrolled={setHasScrolled}
|
||||
blocked={moderation?.blocked}
|
||||
footer={
|
||||
<MessagesListBlockedFooter
|
||||
recipient={recipient}
|
||||
convoId={convoState.convo.id}
|
||||
hasMessages={convoState.items.length > 0}
|
||||
blockInfo={blockInfo}
|
||||
/>
|
||||
convoState.status === ConvoStatus.Disabled ? (
|
||||
<ChatDisabled />
|
||||
) : moderation?.blocked ? (
|
||||
<MessagesListBlockedFooter
|
||||
recipient={recipient}
|
||||
convoId={convoState.convo.id}
|
||||
hasMessages={convoState.items.length > 0}
|
||||
blockInfo={blockInfo}
|
||||
/>
|
||||
) : convoState.pendingAcceptance ? (
|
||||
<MessagesListPendingAcceptFooter
|
||||
recipient={recipient}
|
||||
convoId={convoState.convo.id}
|
||||
hasMessages={convoState.items.length > 0}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
moderateProfile,
|
||||
ModerationOpts,
|
||||
} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
|
||||
@@ -27,7 +27,7 @@ import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
||||
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 {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import * as tokens from '#/alf/tokens'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {ConvoMenu} from '#/components/dms/ConvoMenu'
|
||||
@@ -294,7 +294,14 @@ function ChatListItemReady({
|
||||
|
||||
<View
|
||||
style={[a.flex_1, a.justify_center, web({paddingRight: 45})]}>
|
||||
<View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.flex_row,
|
||||
a.align_end,
|
||||
a.pb_2xs,
|
||||
convo.opened === false && a.pr_4xl,
|
||||
]}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[{maxWidth: '85%'}, web([a.leading_normal])]}>
|
||||
@@ -373,22 +380,50 @@ function ChatListItemReady({
|
||||
/>
|
||||
</View>
|
||||
|
||||
{convo.unreadCount > 0 && (
|
||||
{convo.opened === false &&
|
||||
(isNative || !(showActions || menuControl.isOpen)) ? (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
a.rounded_sm,
|
||||
a.px_sm,
|
||||
native(a.py_2xs),
|
||||
{
|
||||
backgroundColor: isDimStyle
|
||||
? t.palette.contrast_200
|
||||
: t.palette.primary_500,
|
||||
height: 7,
|
||||
width: 7,
|
||||
top: 15,
|
||||
right: 12,
|
||||
top: 12,
|
||||
right: 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
]}>
|
||||
<Text
|
||||
style={[
|
||||
{color: 'white'},
|
||||
a.text_xs,
|
||||
a.font_heavy,
|
||||
web(a.leading_normal),
|
||||
]}>
|
||||
<Trans>NEW</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
convo.unreadCount > 0 && (
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.rounded_full,
|
||||
{
|
||||
backgroundColor: isDimStyle
|
||||
? t.palette.contrast_200
|
||||
: t.palette.primary_500,
|
||||
height: 7,
|
||||
width: 7,
|
||||
top: 15,
|
||||
right: 12,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
EmojiPickerState,
|
||||
} from '#/view/com/composer/text-input/web/EmojiPicker.web'
|
||||
import {List, ListMethods} from '#/view/com/util/List'
|
||||
import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled'
|
||||
import {MessageInput} from '#/screens/Messages/components/MessageInput'
|
||||
import {MessageListError} from '#/screens/Messages/components/MessageListError'
|
||||
import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill'
|
||||
@@ -78,13 +77,11 @@ function onScrollToIndexFailed() {
|
||||
export function MessagesList({
|
||||
hasScrolled,
|
||||
setHasScrolled,
|
||||
blocked,
|
||||
footer,
|
||||
}: {
|
||||
hasScrolled: boolean
|
||||
setHasScrolled: React.Dispatch<React.SetStateAction<boolean>>
|
||||
blocked?: boolean
|
||||
footer?: React.ReactNode
|
||||
footer: React.ReactNode | null
|
||||
}) {
|
||||
const convoState = useConvoActive()
|
||||
const agent = useAgent()
|
||||
@@ -427,11 +424,7 @@ export function MessagesList({
|
||||
/>
|
||||
</ScrollProvider>
|
||||
<Animated.View style={animatedStickyViewStyle}>
|
||||
{convoState.status === ConvoStatus.Disabled ? (
|
||||
<ChatDisabled />
|
||||
) : blocked ? (
|
||||
footer
|
||||
) : (
|
||||
{footer || (
|
||||
<>
|
||||
{isConvoActive(convoState) &&
|
||||
!convoState.isFetchingHistory &&
|
||||
|
||||
@@ -78,10 +78,13 @@ export class Convo {
|
||||
|
||||
private emitter = new EventEmitter<{event: [ConvoEvent]}>()
|
||||
|
||||
private onMarkAsRead: () => void
|
||||
|
||||
convoId: string
|
||||
convo: ChatBskyConvoDefs.ConvoView | undefined
|
||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
recipients: AppBskyActorDefs.ProfileViewBasic[] | undefined = undefined
|
||||
pendingAcceptance: boolean | undefined
|
||||
snapshot: ConvoState | undefined
|
||||
|
||||
constructor(params: ConvoParams) {
|
||||
@@ -89,7 +92,8 @@ export class Convo {
|
||||
this.convoId = params.convoId
|
||||
this.agent = params.agent
|
||||
this.events = params.events
|
||||
this.senderUserDid = params.agent.session?.did!
|
||||
this.senderUserDid = params.agent.session!.did
|
||||
this.onMarkAsRead = params.onMarkAsRead
|
||||
|
||||
this.subscribe = this.subscribe.bind(this)
|
||||
this.getSnapshot = this.getSnapshot.bind(this)
|
||||
@@ -139,6 +143,7 @@ export class Convo {
|
||||
deleteMessage: undefined,
|
||||
sendMessage: undefined,
|
||||
fetchMessageHistory: undefined,
|
||||
pendingAcceptance: undefined,
|
||||
}
|
||||
}
|
||||
case ConvoStatus.Disabled:
|
||||
@@ -156,6 +161,7 @@ export class Convo {
|
||||
deleteMessage: this.deleteMessage,
|
||||
sendMessage: this.sendMessage,
|
||||
fetchMessageHistory: this.fetchMessageHistory,
|
||||
pendingAcceptance: this.pendingAcceptance,
|
||||
}
|
||||
}
|
||||
case ConvoStatus.Error: {
|
||||
@@ -170,6 +176,7 @@ export class Convo {
|
||||
deleteMessage: undefined,
|
||||
sendMessage: undefined,
|
||||
fetchMessageHistory: undefined,
|
||||
pendingAcceptance: undefined,
|
||||
}
|
||||
}
|
||||
default: {
|
||||
@@ -184,6 +191,7 @@ export class Convo {
|
||||
deleteMessage: undefined,
|
||||
sendMessage: undefined,
|
||||
fetchMessageHistory: undefined,
|
||||
pendingAcceptance: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,11 +254,13 @@ export class Convo {
|
||||
case ConvoDispatchEvent.Resume: {
|
||||
this.refreshConvo()
|
||||
this.requestPollInterval(ACTIVE_POLL_INTERVAL)
|
||||
this.markAsRead()
|
||||
break
|
||||
}
|
||||
case ConvoDispatchEvent.Background: {
|
||||
this.status = ConvoStatus.Backgrounded
|
||||
this.requestPollInterval(BACKGROUND_POLL_INTERVAL)
|
||||
this.markAsRead()
|
||||
break
|
||||
}
|
||||
case ConvoDispatchEvent.Suspend: {
|
||||
@@ -513,6 +523,7 @@ export class Convo {
|
||||
convo: ChatBskyConvoDefs.ConvoView
|
||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||
pendingAcceptance: boolean
|
||||
}>
|
||||
| undefined
|
||||
async fetchConvo() {
|
||||
@@ -522,10 +533,11 @@ export class Convo {
|
||||
convo: ChatBskyConvoDefs.ConvoView
|
||||
sender: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||
recipients: AppBskyActorDefs.ProfileViewBasic[]
|
||||
pendingAcceptance: boolean
|
||||
}>(async (resolve, reject) => {
|
||||
try {
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.api.chat.bsky.convo.getConvo(
|
||||
return this.agent.chat.bsky.convo.getConvo(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
},
|
||||
@@ -539,6 +551,7 @@ export class Convo {
|
||||
convo,
|
||||
sender: convo.members.find(m => m.did === this.senderUserDid),
|
||||
recipients: convo.members.filter(m => m.did !== this.senderUserDid),
|
||||
pendingAcceptance: !convo.opened,
|
||||
})
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
@@ -552,11 +565,13 @@ export class Convo {
|
||||
|
||||
async refreshConvo() {
|
||||
try {
|
||||
const {convo, sender, recipients} = await this.fetchConvo()
|
||||
const {convo, sender, recipients, pendingAcceptance} =
|
||||
await this.fetchConvo()
|
||||
// throw new Error('UNCOMMENT TO TEST REFRESH FAILURE')
|
||||
this.convo = convo || this.convo
|
||||
this.sender = sender || this.sender
|
||||
this.recipients = recipients || this.recipients
|
||||
this.pendingAcceptance = pendingAcceptance ?? this.pendingAcceptance
|
||||
} catch (e: any) {
|
||||
logger.error(e, {context: `Convo: failed to refresh convo`})
|
||||
}
|
||||
@@ -592,7 +607,7 @@ export class Convo {
|
||||
|
||||
const nextCursor = this.oldestRev // for TS
|
||||
const response = await networkRetry(2, () => {
|
||||
return this.agent.api.chat.bsky.convo.getMessages(
|
||||
return this.agent.chat.bsky.convo.getMessages(
|
||||
{
|
||||
cursor: nextCursor,
|
||||
convoId: this.convoId,
|
||||
@@ -793,7 +808,7 @@ export class Convo {
|
||||
|
||||
const {id, message} = pendingMessage
|
||||
|
||||
const response = await this.agent.api.chat.bsky.convo.sendMessage(
|
||||
const response = await this.agent.chat.bsky.convo.sendMessage(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
message,
|
||||
@@ -888,7 +903,7 @@ export class Convo {
|
||||
)
|
||||
|
||||
try {
|
||||
const {data} = await this.agent.api.chat.bsky.convo.sendMessageBatch(
|
||||
const {data} = await this.agent.chat.bsky.convo.sendMessageBatch(
|
||||
{
|
||||
items: messageArray.map(({message}) => ({
|
||||
convoId: this.convoId,
|
||||
@@ -935,7 +950,7 @@ export class Convo {
|
||||
|
||||
try {
|
||||
await networkRetry(2, () => {
|
||||
return this.agent.api.chat.bsky.convo.deleteMessageForSelf(
|
||||
return this.agent.chat.bsky.convo.deleteMessageForSelf(
|
||||
{
|
||||
convoId: this.convoId,
|
||||
messageId,
|
||||
@@ -1104,4 +1119,21 @@ export class Convo {
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
async acceptChat() {
|
||||
await this.agent.chat.bsky.convo.updateRead({
|
||||
convoId: this.convoId,
|
||||
})
|
||||
this.pendingAcceptance = false
|
||||
this.onMarkAsRead()
|
||||
}
|
||||
|
||||
markAsRead() {
|
||||
if (!this.pendingAcceptance) {
|
||||
this.agent.chat.bsky.convo.updateRead({
|
||||
convoId: this.convoId,
|
||||
})
|
||||
this.onMarkAsRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useAppState} from '#/lib/hooks/useAppState'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {Convo} from '#/state/messages/convo/agent'
|
||||
import {
|
||||
ConvoParams,
|
||||
@@ -14,8 +15,10 @@ import {
|
||||
} from '#/state/messages/convo/types'
|
||||
import {isConvoActive} from '#/state/messages/convo/util'
|
||||
import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {useMarkAsReadMutation} from '#/state/queries/messages/conversation'
|
||||
import {RQKEY as ListConvosQueryKey} from '#/state/queries/messages/list-conversations'
|
||||
import {
|
||||
RQKEY as ListConvosQueryKey,
|
||||
useOnMarkAsRead,
|
||||
} from '#/state/queries/messages/list-conversations'
|
||||
import {RQKEY as createProfileQueryKey} from '#/state/queries/profile'
|
||||
import {useAgent} from '#/state/session'
|
||||
|
||||
@@ -60,16 +63,20 @@ export function ConvoProvider({
|
||||
const queryClient = useQueryClient()
|
||||
const agent = useAgent()
|
||||
const events = useMessagesEventBus()
|
||||
const onMarkAsRead = useOnMarkAsRead()
|
||||
const stableMAR = useNonReactiveCallback(() => {
|
||||
onMarkAsRead(convoId)
|
||||
})
|
||||
const [convo] = useState(
|
||||
() =>
|
||||
new Convo({
|
||||
convoId,
|
||||
agent,
|
||||
events,
|
||||
onMarkAsRead: stableMAR,
|
||||
}),
|
||||
)
|
||||
const service = useSyncExternalStore(convo.subscribe, convo.getSnapshot)
|
||||
const {mutate: markAsRead} = useMarkAsReadMutation()
|
||||
|
||||
const appState = useAppState()
|
||||
const isActive = appState === 'active'
|
||||
@@ -77,14 +84,12 @@ export function ConvoProvider({
|
||||
React.useCallback(() => {
|
||||
if (isActive) {
|
||||
convo.resume()
|
||||
markAsRead({convoId})
|
||||
|
||||
return () => {
|
||||
convo.background()
|
||||
markAsRead({convoId})
|
||||
}
|
||||
}
|
||||
}, [isActive, convo, convoId, markAsRead]),
|
||||
}, [isActive, convo]),
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -11,6 +11,7 @@ export type ConvoParams = {
|
||||
convoId: string
|
||||
agent: BskyAgent
|
||||
events: MessagesEventBus
|
||||
onMarkAsRead: () => void
|
||||
}
|
||||
|
||||
export enum ConvoStatus {
|
||||
@@ -150,6 +151,7 @@ export type ConvoStateUninitialized = {
|
||||
deleteMessage: undefined
|
||||
sendMessage: undefined
|
||||
fetchMessageHistory: undefined
|
||||
pendingAcceptance: undefined
|
||||
}
|
||||
export type ConvoStateInitializing = {
|
||||
status: ConvoStatus.Initializing
|
||||
@@ -162,6 +164,7 @@ export type ConvoStateInitializing = {
|
||||
deleteMessage: undefined
|
||||
sendMessage: undefined
|
||||
fetchMessageHistory: undefined
|
||||
pendingAcceptance: undefined
|
||||
}
|
||||
export type ConvoStateReady = {
|
||||
status: ConvoStatus.Ready
|
||||
@@ -174,6 +177,7 @@ export type ConvoStateReady = {
|
||||
deleteMessage: DeleteMessage
|
||||
sendMessage: SendMessage
|
||||
fetchMessageHistory: FetchMessageHistory
|
||||
pendingAcceptance: boolean | undefined
|
||||
}
|
||||
export type ConvoStateBackgrounded = {
|
||||
status: ConvoStatus.Backgrounded
|
||||
@@ -186,6 +190,7 @@ export type ConvoStateBackgrounded = {
|
||||
deleteMessage: DeleteMessage
|
||||
sendMessage: SendMessage
|
||||
fetchMessageHistory: FetchMessageHistory
|
||||
pendingAcceptance: boolean | undefined
|
||||
}
|
||||
export type ConvoStateSuspended = {
|
||||
status: ConvoStatus.Suspended
|
||||
@@ -198,6 +203,7 @@ export type ConvoStateSuspended = {
|
||||
deleteMessage: DeleteMessage
|
||||
sendMessage: SendMessage
|
||||
fetchMessageHistory: FetchMessageHistory
|
||||
pendingAcceptance: boolean | undefined
|
||||
}
|
||||
export type ConvoStateError = {
|
||||
status: ConvoStatus.Error
|
||||
@@ -210,6 +216,7 @@ export type ConvoStateError = {
|
||||
deleteMessage: undefined
|
||||
sendMessage: undefined
|
||||
fetchMessageHistory: undefined
|
||||
pendingAcceptance: undefined
|
||||
}
|
||||
export type ConvoStateDisabled = {
|
||||
status: ConvoStatus.Disabled
|
||||
@@ -222,6 +229,7 @@ export type ConvoStateDisabled = {
|
||||
deleteMessage: DeleteMessage
|
||||
sendMessage: SendMessage
|
||||
fetchMessageHistory: FetchMessageHistory
|
||||
pendingAcceptance: boolean | undefined
|
||||
}
|
||||
export type ConvoState =
|
||||
| ConvoStateUninitialized
|
||||
|
||||
Reference in New Issue
Block a user