Skip to content

Commit

Permalink
Merge branch 'develop' into rav-128-setup-required-doctypes-for-creat…
Browse files Browse the repository at this point in the history
…ing-polls-on-raven
  • Loading branch information
janhvipatil committed Apr 1, 2024
2 parents bff0d42 + d80cf99 commit ab15acb
Show file tree
Hide file tree
Showing 120 changed files with 7,668 additions and 2,390 deletions.
2 changes: 1 addition & 1 deletion mobile/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Raven</title>
<title>{{ app_name }}</title>
<meta name="description" content="Simple, work messaging tool.">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
Expand Down
2 changes: 1 addition & 1 deletion mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"clsx": "^2.1.0",
"emoji-picker-element": "^1.21.1",
"firebase": "^10.9.0",
"frappe-react-sdk": "^1.3.11",
"frappe-react-sdk": "^1.5.1",
"highlight.js": "^11.9.0",
"html-react-parser": "^5.1.8",
"input-otp": "^1.2.2",
Expand Down
25 changes: 16 additions & 9 deletions mobile/public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,26 @@ try {
if (payload.data.notification_icon) {
notificationOptions["icon"] = payload.data.notification_icon
}

if (payload.data.raven_message_type === "Image") {
notificationOptions["image"] = payload.data.content
}

if (payload.data.creation) {
notificationOptions["timestamp"] = payload.data.creation
}
const url = `${payload.data.base_url}/raven_mobile/channel/${payload.data.channel_id}`
if (isChrome()) {
notificationOptions["data"] = {
url: payload.data.click_action,
url: url,
}
} else {
if (payload.data.click_action) {
notificationOptions["actions"] = [
{
action: payload.data.click_action,
title: "View",
},
]
}
notificationOptions["actions"] = [
{
action: url,
title: "View",
},
]
}
self.registration.showNotification(notificationTitle, notificationOptions)
})
Expand Down
7 changes: 0 additions & 7 deletions mobile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,6 @@ function App() {

}

useEffect(() => {
//@ts-expect-error
window?.frappePushNotification?.onMessage((payload) => {
showNotification(payload)
})
}, [])

return (
<IonApp>
<FrappeProvider
Expand Down
10 changes: 8 additions & 2 deletions mobile/src/components/features/chat-input/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ import { documentAttachOutline } from 'ionicons/icons';
import { FileUploadModal } from './FileUploadModal';
import { Tiptap } from './Tiptap';
import { AiOutlinePaperClip } from 'react-icons/ai';
import { Haptics, ImpactStyle } from '@capacitor/haptics';

type Props = {
channelID: string,
allMembers: { id: string; value: string }[],
allChannels: { id: string; value: string; }[],
onMessageSend: () => void,
}
export const ChatInput = ({ channelID, allChannels, allMembers, onMessageSend }: Props) => {
export const ChatInput = ({ channelID, allChannels, allMembers }: Props) => {

const { call, loading } = useFrappePostCall('raven.api.raven_message.send_message')

const [files, setFiles] = useState<File[]>([])

const onMessageSend = useCallback(() => {
Haptics.impact({
style: ImpactStyle.Light
})
}, [])

const onSubmit = async (message: string, json: any) => {
return call({
channel_id: channelID,
Expand Down
169 changes: 97 additions & 72 deletions mobile/src/components/features/chat-space/ChatInterface.tsx
Original file line number Diff line number Diff line change
@@ -1,81 +1,77 @@
import { IonBackButton, IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonIcon, IonToolbar, useIonViewWillEnter } from '@ionic/react'
import { useFrappeDocumentEventListener, useFrappeEventListener, useFrappeGetCall } from 'frappe-react-sdk'
import { useCallback, useContext, useMemo, useRef } from 'react'
import { MessagesWithDate } from '../../../../../types/Messaging/Message'
import { IonBackButton, IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonIcon, IonSpinner, IonToolbar, useIonViewWillEnter } from '@ionic/react'
import { useFrappeGetCall } from 'frappe-react-sdk'
import { createContext, useMemo, useRef } from 'react'
import { ErrorBanner } from '../../layout'
import { ChatInput } from '../chat-input'
import { ChatView } from './chat-view/ChatView'
import { ChatHeader } from './chat-header'
import { ChannelListItem, DMChannelListItem, useChannelList } from '@/utils/channel/ChannelListProvider'
import { UserFields } from '@/utils/users/UserListProvider'
import { peopleOutline } from 'ionicons/icons'
import { Haptics, ImpactStyle } from '@capacitor/haptics'
import { UserContext } from '@/utils/auth/UserProvider'
import { arrowDownOutline, peopleOutline } from 'ionicons/icons'
import { ChatLoader } from '@/components/layout/loaders/ChatLoader'
import { MessageActionModal, useMessageActionModal } from './MessageActions/MessageActionModal'
import useChatStream from './useChatStream'
import { useInView } from 'react-intersection-observer'
import { DateSeparator } from './chat-view/DateSeparator'
import { MessageBlockItem } from './chat-view/MessageBlock'
import ChatViewFirstMessage from './chat-view/ChatViewFirstMessage'

export type ChannelMembersMap = Record<string, UserFields>
export const ChannelMembersContext = createContext<ChannelMembersMap>({})

export const ChatInterface = ({ channel }: { channel: ChannelListItem | DMChannelListItem }) => {

const { currentUser } = useContext(UserContext)
const initialDataLoaded = useRef(false)
const conRef = useRef<HTMLIonContentElement>(null);

const scrollToBottom = useCallback((duration = 0, delay = 0) => {
setTimeout(() => {
conRef.current?.scrollToBottom(duration)
}, delay)
}, [])

useIonViewWillEnter(() => {
scrollToBottom(0, 0)
conRef.current?.scrollToBottom()
})

const onNewMessageLoaded = useCallback(() => {
/**
* We need to scroll to the bottom of the chat interface if the user is already at the bottom.
* If the user is not at the bottom, we need to show a button to scroll to the bottom.
*/
if (conRef.current && !initialDataLoaded.current) {
scrollToBottom(0, 100)
initialDataLoaded.current = true
} else {
conRef.current?.getScrollElement().then((scrollElement) => {

const scrollHeight = scrollElement.scrollHeight
const clientHeight = scrollElement.clientHeight
const scrollTop = scrollElement.scrollTop
const isAtBottom = scrollHeight <= scrollTop + clientHeight
if (isAtBottom) {
scrollToBottom(0, 100)
const {
messages,
hasOlderMessages,
loadOlderMessages,
hasNewMessages,
loadNewerMessages,
loadingOlderMessages,
highlightedMessage,
scrollToMessage,
goToLatestMessages,
error,
isLoading } = useChatStream(channel.name, conRef)


const onReplyMessageClick = (messageID: string) => {
scrollToMessage(messageID)
}

const { ref: oldLoaderRef } = useInView({
fallbackInView: true,
initialInView: false,
skip: !hasOlderMessages || loadingOlderMessages,
onChange: (async (inView) => {
if (inView && hasOlderMessages) {
const lastMessage = messages ? messages[0] : null;
await loadOlderMessages()
// Restore the scroll position to the last message before loading more
if (lastMessage?.message_type === 'date') {
document.getElementById(`date-${lastMessage?.creation}`)?.scrollIntoView()
} else {
// setNewMessagesAvailable(true)
document.getElementById(`message-${lastMessage?.name}`)?.scrollIntoView()
}
})
}
})
});

const { ref: newLoaderRef } = useInView({
fallbackInView: true,
skip: !hasNewMessages,
initialInView: false,
onChange: (inView) => {
if (inView && hasNewMessages) {
loadNewerMessages()
}
}

}, [scrollToBottom, conRef])

/**
* We have the channel data. We also have the channel list in a global context.
* Now we need to fetch:
* 1. All the messages in the channel - this is done outside and then sent to the ChatHistory component
* 2. All the users in the channel
*
* */
// Fetch all the messages in the channel

const { messages, error, isLoading } = useChatStream(channel.name, conRef)
// const { data: messages, error: messagesError, mutate: refreshMessages, isLoading: isMessageLoading } = useFrappeGetCall<{ message: MessagesWithDate }>("raven.api.raven_message.get_messages_with_dates", {
// channel_id: channel.name
// }, `get_messages_for_channel_${channel.name}`, {
// keepPreviousData: true,
// onSuccess: (data) => {
// onNewMessageLoaded()
// }
// })
});

const { data: channelMembers } = useFrappeGetCall<{ message: ChannelMembersMap }>('raven.api.chat.get_channel_members', {
channel_id: channel.name
Expand All @@ -85,13 +81,6 @@ export const ChatInterface = ({ channel }: { channel: ChannelListItem | DMChanne
revalidateOnReconnect: false
})

const onMessageSend = () => {
Haptics.impact({
style: ImpactStyle.Light
})
scrollToBottom(0, 100)
}

const { selectedMessage, onMessageSelected, onDismiss } = useMessageActionModal()

const { channels } = useChannelList()
Expand All @@ -106,6 +95,7 @@ export const ChatInterface = ({ channel }: { channel: ChannelListItem | DMChanne
}
return []
}, [channelMembers])

return (
<>
<IonHeader>
Expand All @@ -122,27 +112,62 @@ export const ChatInterface = ({ channel }: { channel: ChannelListItem | DMChanne
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent className='flex flex-col-reverse' fullscreen ref={conRef}>
<IonContent className='flex flex-col' fullscreen ref={conRef}>

<div ref={oldLoaderRef}>
{hasOlderMessages && !isLoading && <div className='flex w-full min-h-8 py-4 justify-center items-center' >
<IonSpinner name='lines' />
</div>}
</div>
{!isLoading && !hasOlderMessages && <ChatViewFirstMessage channel={channel} />}
{isLoading && <ChatLoader />}
{error && <ErrorBanner error={error} />}


{messages &&
<ChatView messages={messages} members={channelMembers?.message ?? {}} onMessageSelected={onMessageSelected} />
<ChannelMembersContext.Provider value={channelMembers?.message ?? {}}>
<div className='flex flex-col'>
{messages.map((message) => {

if (message.message_type === "date") {
return <DateSeparator
key={`date-${message.creation}`}
date={message.creation} />
} else {
return (
<MessageBlockItem
key={`${message.name}_${message.modified}`}
message={message}
isHighlighted={highlightedMessage === message.name}
onReplyMessageClick={onReplyMessageClick}
onMessageSelect={onMessageSelected} />
)
}
}
)}
</div>
</ChannelMembersContext.Provider>
}

{hasNewMessages && <div ref={newLoaderRef}>
<div className='flex w-full min-h-8 pb-4 justify-center items-center'>
<IonSpinner name='lines' />
</div>
</div>}

{/* Commented out the button because it was unreliable. We only scroll to bottom when the user is at the bottom. */}
{/* <IonButton
{hasNewMessages && <IonButton
size='small'
type="button"
onClick={() => scrollToBottom(200, 0)}
hidden={!newMessagesAvailable}
onClick={goToLatestMessages}
shape='round'
// fill="outline"
className="fixed bottom-24 left-1/2 -translate-x-1/2 "
>
New messages
<IonIcon slot="end" icon={arrowDownOutline} />
</IonButton> */}
</IonButton>
}
<div className='h-8'>
</div>
</IonContent>
Expand All @@ -155,7 +180,7 @@ export const ChatInterface = ({ channel }: { channel: ChannelListItem | DMChanne
border-t-zinc-900 border-t-[1px]
pb-6
pt-1'>
<ChatInput channelID={channel.name} allMembers={parsedMembers} allChannels={parsedChannels} onMessageSend={onMessageSend} />
<ChatInput channelID={channel.name} allMembers={parsedMembers} allChannels={parsedChannels} />
</div>
</IonFooter>
<MessageActionModal selectedMessage={selectedMessage} onDismiss={onDismiss} />
Expand Down
39 changes: 0 additions & 39 deletions mobile/src/components/features/chat-space/chat-view/ChatView.tsx

This file was deleted.

Loading

0 comments on commit ab15acb

Please sign in to comment.